spring boot集成security


git地址
https://github.com/a18792721831/studySpringCloud.git

1. security介紹

Spring Security 是Spring Resource 社區的一個安全組件.Sping Secuity爲JavaEE企業級開發提供了全面的安全防護,安全防護是一個不斷變化的目標,Spring Security通過版本不斷迭代來實現這一目標。Spine Sceunt採用"安全層”的概念,使每一層都儘可能安全,連續的安全層可以達到全面的防護。Spring Security可以在Contoller層、Service層,DAO層等以加註解的方式來保護應用程序的安全,Spring Security 提供了細粒度的權限控制,可以精細到每一個API接口、每一個業務的方法,或者每一個操作數據庫的DAO層的方法.Spring Security提供的是應用程序層的安全解決方案,一個系統的安全還需要考患傳輸層和系統層的安全,例如採用Htpps協議、服務器部署防火牆等。

2 爲什麼選擇 Spring Security

使用 Spring Securiy有很多原因,其中一個重要原因是它對環境的無依賴性、低代碼耦合性。將工程重現部署到一個新的服務器上,不需要爲 Spring Security做什麼工作。Spring Security 提供了數十個安全模塊,模塊與模塊間的耦合性低,模塊之間可以自由組合來實現特定需求的安全功能,具有較高的可定製性。總而言之,Spring Security 具有很好的可複用性和可定製性。
在安全方面,有兩個主要的領域,一是“認證”,即你是誰;二是“授權”,即你擁有什麼權限,Spring Security 的主要目標就是在這兩個領域。“認證”是認證主體的過程,通常是指可以在應用程序中執行操作的用戶、設備或其他系統。“授權”是指決定是否允許已認證的主體執行某一項操作。
安全框架多種多樣,那爲什麼選擇 Spring Security 作爲微服務開發的安全框架呢?JavaEE 有另一個優秀的安全框架 Apache Shiro,Apache Shiro 在企業級的項目開發中十分受歡迎,一般使用在單體服務中。但在微服務架構中,目前版本的 Apache Shiro是無能爲力的Spring Security 來自 Spring Resource 社區,採用了註解的方式控制權限,熟悉Spring 的開發者很容易上手Spring Security。另外一個原因就是Spring Security易用與Spring boot工程,也容易集成到Spring Cloud構建的微服務系統中。

總結起來有以下幾個特點:

    1. 代碼耦合低
    1. 模塊化
    1. 控制粒度細
    1. 熟悉spring容易上手
    1. spring boot 或者spring cloud的集成簡單
    1. 龐大的社區與用戶

Spring Security 和Spring Boot Security的關係如下:
在這裏插入圖片描述

3. Security如何使用

3.1 創建

在這裏插入圖片描述

3.2 配置

在這裏插入圖片描述

3.3 security 配置

security需要自己寫一個配置類,配置類集成於WebSecurityConfigureAdapter

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private Environment environment;

    @Value("${web.security.user.name}")
    private String username;

    @Value("${web.security.user.pswd}")
    private String password;

    @Value("${web.security.user.role}")
    private String role;

    @Value("${web.security.admin.name}")
    private String adminName;

    @Value("${web.security.admin.pswd}")
    private String adminPswd;

    @Value("${web.security.admin.role}")
    private String adminRole;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser(username).password("{noop}" + password)
                .roles(role.split(","));
        auth.inMemoryAuthentication().withUser(adminName).password("{noop}" + adminPswd)
                .roles(adminRole.split(","));

    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/css/**", "/index").permitAll()
                .antMatchers("/user/**").hasRole("USER")
                .antMatchers("/admin/**").hasRole("ADMIN")
                .and()
                .formLogin().loginPage("/login").failureUrl("/login-error")
                .and()
                .exceptionHandling().accessDeniedPage("/401")
                .and()
                .logout().logoutSuccessUrl("/");
    }

}

這裏的私有屬性是在config.properties裏面配置的用戶名、密碼與權限的信息。
這裏最好不要硬編碼。
在這裏插入圖片描述

3.3.1 configureGlobal方法

這個方法中,在內存中創建2個用戶的信息,用戶的用戶名、密碼以及密碼的加密方式,和其具有的角色。
密碼的加密方式:
在這裏插入圖片描述
這個方法裏只有短短的兩行代碼,但是其完成了非常多的操作:

    1. 應用的每一個請求都要認證
    1. 自動生成了一個登陸表單
    1. 用指定的用戶名密碼進行認證
    1. 用戶可以註銷
    1. 阻止了CSRF的攻擊
    1. Session Fixation的保護
    1. 安全Header
    • HTTP Strict Transport Security for secure requests
    • X-Content-Type_Options integration
    • Cache Control
    • X-XSS-Protection integration
    • XFrake-Option integration to help prevent Clickjacking
    1. 集成了如下方法:
    • HttpServletRequest#getRemoteUser()
    • HttpServletRequest.html#getUserPrincipal()
    • HttpServletRequest.html#isUserInRole(String)
    • HttpServletRequest.html#login(String,String)
    • HttpServletRequest.html#logout()

3.3.2 啓動登陸

在這裏插入圖片描述
其源碼如下
在這裏插入圖片描述
寫一個簡單的html界面用來標識登陸成功。
使用admin登陸
在這裏插入圖片描述

3.3.3 自定義配置 configure

代碼 配置內容
“/css/**”,"/index" 不需要認證即可訪問
“/user/**” user目錄下的界面需要驗證user角色
“/admin” admin目錄下的界面需要驗證admin角色
formLogin 表單登陸界面是/login界面
failureUrl 登陸失敗的地址是/login-error
exceptionHandling 異常會被重定向到401界面
logout 支持註銷
logoutSuccessUrl 註銷後重定向到/

3.3.4 controller

基於3.3.3的配置,實現controller

@Controller
public class MainController {

    @RequestMapping("/")
    public String root(){
        return "redirect:/index";
    }

    @RequestMapping("/index")
    public String index(){
        return "index";
    }

    @RequestMapping("/user/index")
    public String userIndex(){
        return "user/index";
    }

    @RequestMapping("/admin/index")
    public String adminIndex(){
        return "admin/index";
    }

    @RequestMapping("/login")
    public String login(){
        return "login";
    }

    @RequestMapping("/login-error")
    public String loginError(Model model){
        model.addAttribute("loginError",true);
        return "login";
    }

    @GetMapping("/401")
    public String accessDenied(){
        return "401";
    }
}

3.3.5 界面

login.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Login page</title>
    <base href="/">
    <link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}" />
</head>
<body>
    <h1>Login page</h1>
    <p th:if="${loginError}" class="error">用戶名或者密碼錯誤!</p>
    <form th:action="@{/login}" method="post">
        <label for="username">用戶名</label>:
        <input type="text" id="username" name="username" autofocus="autofocus"/><br/>
        <label for="password">密  碼</label>:
        <input type="password" id="password" name="password" autofocus="autofocus"/><br/>
        <input type="submit" value="登錄"/>
    </form>
    <p><a th:href="@{/index}">返回首頁</a> </p>
</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8">
    <title>Hello Spring Boot Security for index</title>
    <base href="/">
    <link rel="stylesheet" href="css/main.css" th:href="@{/css/main.css}"/>
</head>
<body>
    <h1>Hello Spring Boot Security for index</h1>
    <p>這個界面沒有受到保護.</p>
    <div th:fragment="logout" sec:authorize="isAuthenticated()">
        登錄用戶:<span sec:authentication="name"/>
        用戶角色:<span sec:authentication="principal.authorities"/>
        <div>
            <form action="#" th:action="@{/logout}" method="post">
                <input type="submit" value="登出" />
            </form>
        </div>
    </div>
    <ul>
        <li>點擊<a href="/user/index" th:href="@{/user/index}">去/user/index被保護的界面</a> </li>
        <li>點擊<a href="/admin/index" th:href="@{/admin/index}">去/admin/index被保護的界面</a> </li>
    </ul>
</body>
</html>

401.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
    <meta charset="UTF-8">
    <title>401 Page</title>
</head>
<body>
    <div>
        <div>
            <h2>權限不夠</h2>
        </div>
        <div sec:authorize="isAuthenticated()">
            <p>已有用戶登錄</p>
            <p>用戶:<span sec:authentication="name" /></p>
            <p>角色:<span sec:authentication="principal.authorities"/></p>
        </div>
        <div sec:authorize="isAnonymous()">
            <p>未有用戶登錄</p>
        </div>
        <p>
            拒絕訪問!
        </p>
    </div>
</body>
</html>

/user/index.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Hello Spring Security, User Index</title>
    <base href="/">
    <link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}"/>
</head>
<body>
    <div th:substituteby="index::logout"/>
    <h1>這個界面是被保護界面,user角色可以訪問</h1>
    <p><a href="/index" th:href="@{/index}">返回首頁</a> </p>
    <p><a href="/admin" th:href="@{/admin/index}">去admin目錄下的index</a> </p>
</body>
</html>

/admin/index.html

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Hello Spring Security, Admin Index</title>
    <base href="/">
    <link rel="stylesheet" href="/css/main.css" th:href="@{/css/main.css}"/>
</head>
<body>
<div th:substituteby="index::logout"/>
<h1>這個界面是被保護界面,admin角色可以訪問</h1>
<p><a href="/index" th:href="@{/index}">返回首頁</a> </p>
<p><a href="/admin" th:href="@{/user/index}">去user目錄下的index</a> </p>
</body>
</html>

3.4 啓動

在這裏插入圖片描述
不登陸訪問/user或者admin的界面
在這裏插入圖片描述
訪問admin的界面登陸user用戶(user用戶只有user角色)
在這裏插入圖片描述
相反的,訪問user界面,登陸admin用戶(admin用戶有user和admin的角色)
在這裏插入圖片描述
訪問admin下的界面
在這裏插入圖片描述
然後登出
在這裏插入圖片描述
登陸user角色訪問user界面
登陸失敗
在這裏插入圖片描述
在這裏插入圖片描述
然後訪問admin的界面
在這裏插入圖片描述

4. security 方法保護

4.1 創建實體

public class Student {

    private String name;

    private int age;

    private String like;

    private Student(){

    }

    public static Student getBuild(){
        return new Student();
    }

    public Student name(String name){
        this.name = name;
        return Student.this;
    }

    public Student age(int age){
        this.age = age;
        return Student.this;
    }

    public Student like(String like){
        this.like = like;
        return Student.this;
    }

    public String getName(){
        return this.name;
    }

    public int getAge(){
        return this.age;
    }

    public String getLike(){
        return this.like;
    }

}

4.2 創建服務

在這裏插入圖片描述

4.3 創建controller

在這裏插入圖片描述

4.4 訪問驗證

可以看到,我們在service上有兩個訪問,一個是獲取全部的學生的getStudentList的方法,這個方法只要有任意一個權限就能夠訪問。而另一個方法則必須擁有ADMIN的權限的用戶登錄才能進行訪問。
首先以USER權限進行登錄:
在這裏插入圖片描述
然後獲取所有的用戶
在這裏插入圖片描述
然後進行嘗試刪除學生–小美
在這裏插入圖片描述
發現其在controller接收到請求調用服務時,因權限不夠而發生異常,但是我們之前在配置時配置,當有異常出現時,自動重定向到401的界面。
所以,其展示的urlk地址是刪除的地址,但是界面的內容確是,401的內容。
接下來使用admin權限的用戶進行登錄,然後嘗試刪除學生。
在這裏插入圖片描述
在這裏插入圖片描述
這裏沒有任何返回值,表示已經刪除成功了,接下來重新獲取所有的學生:
在這裏插入圖片描述

5. 從數據庫中讀取用戶認證信息

5.1 創建

在這裏插入圖片描述
爲了防止因爲字符集的問題,需要手動增加依賴
在這裏插入圖片描述

5.2 配置

在這裏插入圖片描述

5.3 創建實體

在這裏插入圖片描述
在這裏插入圖片描述

@Entity
public class Subscriber implements UserDetails, Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false, unique = true)
    private String username;

    /*
     * OneToMany 是一對多的關係,關係由多的記錄的屬性維護(一般情況)
     * ManyToMany 是多對多的關係,關係由中間關係表維護(一般情況)
     */

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name = "subscriber_role", joinColumns = @JoinColumn(name = "subscriber_id", referencedColumnName = "id"),
    inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
    /*
     * 這個JoinTable的大概含義是:
     * 這個中間關係由關係表維護,表名是 user_role
     * 關係表有兩個字段,一個是 user_id,其映射的值是user表的id
     * 另一個是role_id,其映射的值是role表的id
     */
    private List<Role> authorities;

    public Subscriber(){

    }

    public Long getId(){
        return id;
    }

    public void setId(Long id){
        this.id = id;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    public void setAuthorities(List<Role> authorities){
        this.authorities = authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    public void setPassword(String password){
        this.password = password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    public void setUsername(String username){
        this.username = username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

5.4 dao

在這裏插入圖片描述

5.5 service

在這裏插入圖片描述

5.6 config

在這裏插入圖片描述

5.7 啓動驗證

這是啓動項目前的數據庫中所有的表
在這裏插入圖片描述
接着啓動
在這裏插入圖片描述
在這裏插入圖片描述
中間關係表與我們猜想的一致
在這裏插入圖片描述
不過有一點沒有想到,這個中間關係表竟然有外鍵。

用戶 權限
userA USER
userB USER
userC USER
adminA ADMIN
adminB ADMIN
adminC ADMIN
allA USER
allA ADMIN
allB USER
allB ADMIN
allC USER
allC ADMIN

我們插入上述數據:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
接下來用這些用戶嘗試登陸,並且結合4中的邏輯,進行驗證。
在這裏插入圖片描述

改動點如上圖所示
在這裏插入圖片描述
登陸
userA
在這裏插入圖片描述
在這裏插入圖片描述
adminA
在這裏插入圖片描述
在這裏插入圖片描述
allA
在這裏插入圖片描述
在這裏插入圖片描述

注意點:
這裏面有兩個坑:
1.password需要返回加密方式:
在這裏插入圖片描述
原因:
在這裏插入圖片描述
可選
在這裏插入圖片描述
2.role返回的時候需要加前綴
在這裏插入圖片描述
原因:
在這裏插入圖片描述
使用配置的時候會自動加這個前綴,現在使用jpa則不會自動加前綴

6. 總結

使用Spring Security 還是比較簡單的,沒有想象中那麼複雜。首先引入 Spring Security相關的依賴,然後寫一個配置類,該配置類繼承了 WebSecurityConfigurerAdapter,並在該配置類上加@EnableWebSecurity 註解開啓 Web Security。再需要配置 AuthenticationManagerBuilder,AuthenticationManagerBuilder 配置了讀取用戶的認證信息的方式,可以從內存中讀取,也可以
從數據庫中讀取,或者用其他的方式。其次,需要配置 HttpSecurity,HttpSecurity 配置了請求的認證規則,即哪些 URI 請求需要認證、哪些不需要,以及需要擁有什麼權限才能訪問。最後,如果需要開啓方法級別的安全配置,需要通過在配置類上加@EnableGlobalMethodSccuriy註解開啓,方法級別上的安全控制支持secureEnabled、jsr250Enabled和 prePostEnabled這3種
方式,用的最多的是prePostEnabled。其中,prePostEnabled 包括PreAuthorize和 PostAuthorize兩種形式,一般只用到PreAuthorize這種方式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章