【Spring Boot】Spring Boot的Security安全控制

Spring Boot的Security安全控制

Spring Security是一個強大且高度可定製的身份驗證和訪問控制框架,完全基於Spring的應用程序的標準,Spring Security爲基於JAVA EE的企業應用程序提供了一個全面的安全解決方案。

Spring Security是什麼?

Spring Security提供了一組可以在Spring應用上下文中配置的Bean,充分利用了Spring IoC(控制反轉)和AOP(面向切面編程)功能,爲應用系統提供安全訪問控制功能。
安全框架主要包括兩個操作:

  • 認證(Authentication):確定用戶可以訪問當前系統
  • 授權(Authorization):確定用戶在當前系統中是否能夠執行某個操作,
    Spring Security包括多個模塊:
  • 核心模塊(spring-security-core.jar):包含核心的驗證和訪問控制類以及接口、遠程支持和基本的配置API。
  • 遠程調用(spring-security-remoting.jar):提供與Spring Remoting的集成
  • Web頁面(spring-security-web.jar):包括網站安全相關的基礎代碼,包括Spring Security網頁驗證服務和基於URL的訪問控制
  • 配置(spring-security-config.jar):包含安全命令空間的解析代碼
  • LDAP(spring-security-ldap.jar):LDAP驗證和配置代碼,如果使用LDAP驗證和管理LDAP用戶實體,需要使用該模塊
  • ACL訪問控制表(spring-security-acl.jar):ACL專門的領域對象的實現,用於在應用程序中對特定域對象實例應用安全性。
  • CAS(spring-security-cas.jar):Spring Security的CAS客戶端集成,用於CAS的SSO服務器使用Spring Security網頁驗證
  • OpenID(spring-security-openid.jar):OpenID網頁驗證支持,使用外部的OpenID服務器驗證用戶。
  • Test(spring-security-test.jar):支持Spring Security的測試。

Spring Security基礎

Security適配器

在Spring Boot當中配置Spring Security非常簡單,創建一個自定義類集成WebSecurityConfigurerAdapter,並在該類中使用@EnableWebSecurity註解,就可以通過重寫config方法類配置所需要的安全配置
WebSecurityConfigurerAdapter是Spring Security爲Web應用提供的一個適配器,實現了WebSecurityConfigurer接口,提供了兩個方法用於重寫開發者需要的安全配置:

protected void configure(HttpSecurity httpSecurity) throws Exception{}
protected void configure(AuthenticationManagerBuilder auth) throws Exception{}

configure(HttpSecurity httpSecurity)方法中可以通過HttpSecurity的authorizeRequests()方法定義哪些URL需要被保護,哪些不需要被保護;通過formLogin()方法定義當需要用戶登錄的時候,跳轉到的登錄頁面。
configure(AuthenticationManagerBuilder auth)方法用於創建用戶和用戶的角色。

用戶認證

Spring Security是通過configureGlobal(AuthenticationManagerBuilder auth)完成用戶認證的。使用AuthenticationManagerBuilder的inMemoryAuthentication()方法可以添加用戶,並給用戶指定權限。
例如:

@Authwired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
	auth.inMemoryAuthentication().withUser("fkit").password("1234455").roles("USER");
	auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN","DBA");
}

需要注意的是,Spring Security保存用戶權限的時候,會默認使用“ROLE_”,也就是說,"USER"實際上是“ROLE_UAER“。”ADMIN”實際上是“ROLE_ADMIN”

用戶授權

Spring Security是通過configure(HttpSecurity http)完成用戶授權的。
HttpSecurity的authorizeRequests()方法有多個子節點,每個macher按照它們的聲明順序執行,指定用戶可以訪問的多個URL模式。

  • antMatchers使用Ant風格匹配路徑
  • regrexMatchers使用正則表達式匹配路徑
    在匹配了請求路徑後,可以針對當前用戶的信息對請求路徑進行安全處理。如下表所示的安全處理方法
方法 用途
anyRequest 匹配所有請求路徑
access(String) Spring EL表達式結果爲true時可以訪問
anonymous() 匿名可以訪問
denyAll() 用戶不能訪問
fullyAuthenticated() 用戶完全認證可以訪問(非remember-me下自動登錄)
hasAnyAuthority(String…) 如果有參數,參數表示權限,則其中任何一個權限可以訪問
hasAnyRole(String…) 如果有參數,參數表示角色,則其中任何一個角色可以訪問
hasAuthority(String…) 如果有參數,參數表示權限,則其權限可以訪問
hasIpAddress(String…) 如果有參數,參數表示IP地址,如果用戶IP和參數匹配,則可以訪問
hasRole(String) 如果有參數,參數表示角色,則其角色可以訪問
permitAll() 用戶可以任意訪問
rememberMe() 允許通過remember-me登錄的用戶訪問
authenticated() 用戶登錄後可以訪問

示例代碼如下:

@Override
protected void configure(HttpSecurity http) throws Exception{
	http.authorizeRequests() // 開始請求權限配置
	.antMatchers("/login").permitAll() // 請求匹配/login,所有用戶都可以訪問
	.antMatchers("/","/home").hasRole("USER") // 請求匹配/和/home,擁有ROLE_USER角色的用戶可以訪問
	.antMatchers("/admin/***").hasAnyRole("ADMIN","DBA") //請求匹配/admin/**,擁有ROLE_ADMIN或ROLE_DBA角色的用戶可以訪問
	.anyRequst().authenticated(); //其餘所有的請求都需要認證之後纔可以訪問。
}

HttpSecurity還可以設置登錄的行爲,示例代碼如下:

@Override
protected void configure(HttpSecurity http) throws Exception{
	http.authorizeRequests()
	.antMatchers("/login").permitAll()
	.antMatchers("/","/home").hasRole("USER")
	.antMatchers("/admin/***").hasAnyRole("ADMIN","DBA")
	.anyRequest().authenticated()
	.and()
	.formLogin()// 開始設置登錄操作
	.loginPage("/login") //設置登錄頁面的訪問地址
	.usernameParameter("/loginName").passwordParameter("password") //登錄時接收傳遞的參數loginName的值作爲用戶名,接收傳遞參數的password的值作爲密碼
	.defaultSuccessUrl("/success") //指定登錄成功後轉向的頁面
	.failureUrl("/login?error") //指定登錄失敗後轉向的頁面和傳遞的參數
	.and()
	.logout() //設置註銷操作
	.permitAll() //所有用戶都可以訪問
	.and()
	.exceptionHandling().accessDeniedPage("/accessDenied"); // 指定異常處理頁面
}

Spring Security核心類

Spring Security核心類包括Authentication、SecurityContextHolder、UserDetails、UserDetailsService、GrantedAuthority、DaoAuthenticationProvider和PasswordEncoder。

  1. Authentication: 用來表示用戶認證的信息,在用戶登錄認證之前,Spring Security會將相關信息封裝爲一個Authentication具體實現類的對象,在登錄認證成功之後又會生成一個信息更全面,包含用戶權限等信息的Authentication對象,然後把它保存在SecurityContextHolder所持有的SecurityContext中,供後續的程序進行調用,如訪問權限的鑑定等。
  2. SecurityContextHolder:用來保存SecurityContext的。SecurityContext中包含當前所訪問系統的用戶的詳細信息。默認情況下,SecurityContextHolder將使用ThreadLocal來保存SecurityContext,這也意味着在處於同一線程的方法中,可以從ThreadLocal獲取到當前的SecurityContext。
    Spring Security使用一個Authentication對象來描述當前用戶的相關信息。SecurityContextHolder中持有的是當前用戶的SecurityContext,而SecurityContext持有的是代表當前用戶相關信息的Authentication的引用。這個Authentication對象不需要我們自己去創建,在與系統交互的過程中,Spring Security會自動爲我們創建相應的Authentication對象,然後賦值給當前的SecurityContext。但是往往我們需要在程序中獲取當前用戶的相關信息,比如最常見的是獲取當前登錄用戶的用戶名。例如:
String username = SecurityContextHolder.getContext().getAuthentication().getName();
  1. UserDetails:是一個Spring Security的一個核心接口。其中定義了一些可以獲取用戶名、密碼、權限等與認證相關的信息的方法。Sprign Security內部使用的UserDetails實現類大都是內置的User類,要使用UserDetails,也可以直接使用該類。在Spring Security內部,很多需要使用用戶信息的時候,基本上都是使用UserDetails,比如在登錄認證的時候。
    登錄認證的時候Spring Security會通過UserDetailsService的loadUserByUsername()方法獲取對應的UserDetails進行認證,認證通過後會將該UserDetails賦給認證通過的Authentication的principal,然後再把該Authentication存入到SecurityContext中。之後如果需要使用用戶信息的時候就是通過SecurityContextHolder獲取存放在SecurityContext中的Authentication的principal。
    通常我們需要在應用中獲取當前用戶的其它信息,如Email、電話等。這時存放在Authentication的principal中只包含有認證相關信息的UserDetails對象可能就不能滿足我們的要求了。這時我們可以實現自己的UserDetails,在該實現類中我們可以定義一些獲取用戶其它信息的方法,這樣將來我們就可以直接從當前SecurityContext的Authentication的principal中獲取這些信息了。UserDetails是通過UserDetailsService的loadUserByUsername()方法進行加載的。UserDetailsService也是一個接口,我們也需要實現自己的UserDetailsService來加載我們自定義的UserDetails信息,然後把它指定給AuthenticationProvider即可。
  2. UserDetailsService
    通過Authentication.getPrincipal()的返回類型是Object,但很多情況下其返回的其實是一個UserDetails的實例。UserDetails是Spring Security中一個核心的接口。其中定義了一些可以獲取用戶名、密碼、權限等與認證相關的信息的方法。Spring Security內部使用的UserDetails實現類大都是內置的User類,我們如果要使用UserDetails時也可以直接使用該類。在Spring Security內部很多地方需要使用用戶信息的時候基本上都是使用的UserDetails,比如在登錄認證的時候。登錄認證的時候Spring Security會通過UserDetailsService的loadUserByUsername()方法獲取對應的UserDetails進行認證,認證通過後會將該UserDetails賦給認證通過的Authentication的principal,然後再把該Authentication存入到SecurityContext中。之後如果需要使用用戶信息的時候就是通過SecurityContextHolder獲取存放在SecurityContext中的Authentication的principal。
  3. GrantedAuthority
    Authentication的getAuthorities()可以返回當前Authentication對象擁有的權限,即當前用戶擁有的權限。其返回值是一個GrantedAuthority類型的數組,每一個GrantedAuthority對象代表賦予給當前用戶的一種權限。GrantedAuthority是一個接口,其通常是通過UserDetailsService進行加載,然後賦予給UserDetails的。
    GrantedAuthority中只定義了一個getAuthority()方法,該方法返回一個字符串,表示對應權限的字符串表示,如果對應權限不能用字符串表示,則應當返回null。
    Spring Security針對GrantedAuthority有一個簡單實現SimpleGrantedAuthority。該類只是簡單的接收一個表示權限的字符串。Spring Security內部的所有AuthenticationProvider都是使用SimpleGrantedAuthority來封裝Authentication對象。
  4. DaoAuthenticationProvider
    Spring Security默認會使用DaoAuthenticationProvider實現AuthenticationProvider接口,專門進行用戶認證的處理。DaoAuthenticationProvider在進行認證的時候需要一個UserDetailsService來獲取用戶的信息UserDetails,其中包括用戶名、密碼和所擁有的權限等。如果需要改變認證的方式,開發者可以實現自己的AuthenticationProvider。
  5. PasswordEncoder
    在Spring Security中,對密碼的加密都是由PasswordEncoder來完成的。在Spring Security中,已經對PasswordEncoder有了很多實現,包括MD5加密、SHA-256加密等,開發者只需要拿來用就好。在DaoAuthenticationProvider中,有一個就是PasswordEncoder屬性,密碼加密功能主要靠它來完成。
    在Spring的官方文檔中,如果開發一個新的項目,BCryptPasswordEncoder是較好的選擇。BCryptPasswordEncoder使用BCrypt的強散列哈希加密實現,並可以由客戶端指定加密的強度,強度越高安全性自然就越高。

Spring Security的驗證機制

Spring Security大體上是由一堆Filter實現的,Filter會在Spring MVC前攔截請求。Filter包括登出Filter(LogoutFilter)、用戶名密碼驗證Filter(UsernamePasswordAuthenticationFilter)之類。Filter再交由其他組件完成細分的功能,最常用的UsernamePasswordAuthenticationFilter會持有一個AuthenticationManager引用,AuthenticationManager是一個驗證管理器,專門負責驗證。但AuthenticationManager本身並不做具體的驗證工作,AuthenticationManager持有一個AuthenticationProvider集合,AuthenticationProvider纔是驗證工作的組件,驗證成功或失敗之後調用對應的Handler。

Spring Boot的支持

  • Spring Boot 通過org.springframework.boot.autoconfigure.security包對Spring Security提供了自動裝配的支持,其工作主要通過SecurityAutoConfiguration和SecurityProperties兩個類來完成自動裝配。
    SecurityProperties類使用以“security"爲前綴的屬性配置Spring Security相關的配置,具體內容如下:
security.user.name=user # 默認用戶名
security.user.password= # 默認用戶密碼
security.user.role=USER # 默認用戶角色
security.require-ssl=false # 是否需要ssl支持
security.enable-csrf=false # 是否開啓csrf支持,默認關閉
# 默認basic認證設置
security.basic.enabled=true
security.basic.realm=Spring
security.basic.path=  #/**
# 默認headers認證設置
security.headers.xss=false
security.headers.cache=false
security.headers.frame=false
security.headers.contentType=false
security.headers.hsts=all
security.sessions=stateless # Session創建策略(always,never,if_required,stateless)
security.ignored=false # 安全策略
  • Spring Boot自動配置一個DefaultSecurityFilterChain過濾器,用來忽略/css/**, /js/**、/images/**、/webjars/**、/**/favicon.ico、/error等文件的攔截。
  • Spring Boot自動註冊Security的過濾器。

簡單Spring Boot Security應用

創建一個新的Maven項目,命名爲SpringSecurity。按照Maven項目的規範,在src/main/下新建一個名爲resources的文件夾,並在src/main/resources下新建static和templates兩個文件夾。

  1. 修改pom.xml文件,並引入靜態文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.security</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Sprign Security Demo project for Spring Boot</description>

    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
            <scope>true</scope>
        </dependency>
        <dependency>
            <groupId>org.mariadb.jdbc</groupId>
            <artifactId>mariadb-java-client</artifactId>
            <version>2.1.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  1. 開發用於測試的html頁面
    login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="zh">
<head>
    <meta charset="UTF-8"/>
    <title>Spring Boot Security 示例</title>
    <link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
    <link rel="stylesheet" th:href="@{css/bootstrap-theme.min.css}"/>
    <script type="text/javascript" th:src="@{js/jQuery-3.4.1.min.js}"></script>
    <script type="text/javascript" th:src="@{js/bootstrap.min.js}"></script>
    <script type="text/javascript">
        $(function () {
            $("#loginBtn").click(function () {
                var loginName = $("#loginName");
                var password = $("#password");
                var msg = "";
                if (loginName.val() === "") {
                    msg = "登錄名不能爲空!";
                    loginName.focus();
                } else if (password.val() === "") {
                    msg = "密碼不能爲空!";
                    password.focus();
                }
                if (msg !== "") {
                    alert(msg);
                    return false;
                }
                $("#loginForm").submit();
            });
        });
    </script>
</head>
<body>
<div class="panel panel-primary">
    <!-- .panel-heading 面板頭信息 -->
    <div class="panel-heading">
        <!-- .panel-title 面板標題 -->
        <h3 class="panel-title">簡單Spring Boot Security示例</h3>
    </div>
</div>
<div id="mainWrapper">
    <div class="login-container">
        <div class="login-card">
            <div class="login-form">
                <form class="form-horizontal" th:action="@{/login}" method="post" id="loginForm">
                    <!-- 用戶名或密碼錯誤提示-->
                    <div th:if="${param.error != null}">
                        <div class="alert alert-danger">
                            <p><font color="red">用戶名或密碼錯誤!</font></p>
                        </div>
                    </div>
                    <!-- 註銷提示 -->
                    <div th:if="${param.logout !=null}">
                        <div class="alert alert-success">
                            <p><font color="red">用戶已註銷成功!</font></p>
                        </div>
                    </div>
                    <div class="input-group input-sm">
                        <label class="input-group-addon"><i class="fa fa-user"></i></label>
                        <input class="form-control" placeholder="請輸入用戶名" type="text" name="loginName" id="loginName"/>
                    </div>
                    <div class="input-group input-sm">
                        <label class="input-group-addon"><i class="fa fa-lock"></i></label>
                        <input class="form-control" placeholder="請輸入密碼" type="password" name="password" id="password"/>
                    </div>
                    <div class="form-actions">
                        <input id="loginBtn" type="button" class="btn btn-block btn-primary btn-default" value="登錄"/>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>
</body>
</html>

home.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <meta charset="UTF-8">
    <title>home 頁面</title>
    <link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
    <link rel="stylesheet" th:href="@{css/bootstrap-theme.min.css}"/>
    <script type="text/javascript" th:src="@{js/jQuery-3.4.1.min.js}"></script>
    <script type="text/javascript" th:src="@{js/bootstrap.min.js}"></script>
</head>
<body>
<div class="panel panel-primary">
    <div class="panel-heading">
        <h3 class="panel-title">Home 頁面</h3>
    </div>
</div>
<h3>歡迎[<font color="red"><span th:text="${user}">用戶名</span></font> ]訪問Home頁面!
    您的權限是<font color="red"><span th:text="${role}">權限</span></font><br/><br/>
    <a href="admin">訪問admin頁面</a><br/><br/>
    <a href="logout">安全退出</a>
</h3>
</body>
</html>

admin.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <meta charset="UTF-8">
    <title>admin 頁面</title>
    <link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
    <link rel="stylesheet" th:href="@{css/bootstrap-theme.min.css}"/>
    <script type="text/javascript" th:src="@{js/jQuery-3.4.1.min.js}"></script>
    <script type="text/javascript" th:src="@{js/bootstrap.min.js}"></script>
</head>
<body>
<div class="panel panel-primary">
    <div class="panel-heading">
        <h3 class="panel-title">Admin 頁面</h3>
    </div>
</div>
<h3>歡迎[<font color="red"><span th:text="${user}">用戶名</span></font> ]訪問Admin頁面!
    您的權限是<font color="red"><span th:text="${role}">權限</span></font><br/><br/>
    <a href="dba">訪問dba頁面</a><br/><br/>
    <a href="logout">安全退出</a>
</h3>
</body>
</html>

dba.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <meta charset="UTF-8">
    <title>dba 頁面</title>
    <link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
    <link rel="stylesheet" th:href="@{css/bootstrap-theme.min.css}"/>
    <script type="text/javascript" th:src="@{js/jQuery-3.4.1.min.js}"></script>
    <script type="text/javascript" th:src="@{js/bootstrap.min.js}"></script>
</head>
<body>
<div class="panel panel-primary">
    <div class="panel-heading">
        <h3 class="panel-title">DBA 頁面</h3>
    </div>
</div>
<h3>歡迎[<font color="red"><span th:text="${user}">用戶名</span></font> ]訪問DBA頁面!
    您的權限是<font color="red"><span th:text="${role}">權限</span></font><br/><br/>
    <a href="logout">安全退出</a>
</h3>
</body>
</html>

accessDenied.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <meta charset="UTF-8">
    <title>訪問拒絕頁面</title>
    <link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
    <link rel="stylesheet" th:href="@{css/bootstrap-theme.min.css}"/>
    <script type="text/javascript" th:src="@{js/jQuery-3.4.1.min.js}"></script>
    <script type="text/javascript" th:src="@{js/bootstrap.min.js}"></script>
</head>
<body>
<div class="panel panel-primary">
    <div class="panel-heading">
        <h3 class="panel-title">AccessDenied 頁面</h3>
    </div>
</div>
<h3>歡迎[<font color="red"><span th:text="${user}">用戶名</span></font> ],您沒有權限訪問頁面!
    您的權限是<font color="red"><span th:text="${role}">權限</span></font><br/><br/>
    <a href="logout">安全退出</a>
</h3>
</body>
</html>
  1. 創建Spring Security的認證處理類
    使用自定義密碼編輯器,必須實現PasswordEncoder接口。
package com.security.demo.security;

import org.springframework.security.crypto.password.PasswordEncoder;

public class MyPasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence charSequence) {
        return charSequence.toString();
    }

    @Override
    public boolean matches(CharSequence arg0, String arg1) {
        return arg1.equals(arg0.toString());
    }
}

AppSecurityConfigurer類是示例中最關鍵的一個類,用於處理Spring Security的用戶認證和授權操作:

package com.security.demo.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class AppSecurityConfigurer extends WebSecurityConfigurerAdapter {
    /**
     * 注入認證處理類,處理不同用戶跳轉到不同的頁面
     */
    @Autowired
    AppAuthenticatonSuccessHandler appAuthenticatonSuccessHandler;

    /**
     * 用戶授權操作
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        System.out.println("AppSecurityConfigurer configure() 調用......");
        http.authorizeRequests()
                .antMatchers("/login", "/css/**", "/js/**", "/img/*").permitAll()
                .antMatchers("/", "/home").hasRole("USER")
                .antMatchers("/admin/**").hasAnyRole("ADMIN","DBA")
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
                .formLogin().loginPage("/login")
                .successHandler(appAuthenticatonSuccessHandler)
                .usernameParameter("loginname").passwordParameter("password")
                .and()
                .logout().permitAll()
                .and()
                .exceptionHandling().accessDeniedPage("/accessDenied");
    }

    /**
     * 用戶認證操作
     */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        System.out.println("AppSecurityConfigurer configureGlobal() 調用......");
        auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder()).withUser("fkit").password("123456").roles("USER");
        auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder()).withUser("admin").password("admin").roles("ADMIN", "DBA");
    }

}
  1. 創建認證成功處理類
    AppAuthenticationSuccessHandler類繼承了SimpleUrlAuthenticationSuccessHandler類,該類繼承了AbstractAuthenticationTargetUrlRequestHandler父類,實現了AuthenticationSuccessHandler接口,是Spring用來處理用戶認證授權並跳轉到指定URL的。
package com.security.demo.security;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Component
public class AppAuthenticatonSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
    // Spring Security通過RedirectStrategy對象負責所有重定向事務
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    /**
     * 重寫handle方法,方法中通過RedirectStrategy對象重定向到指定的URL
     */
    @Override
    protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        // 通過determinTargetUrl方法返回需要跳轉的URL
        String targetUrl = determineTargetUrl(authentication);
        // 重定向請求到指定的URL
        redirectStrategy.sendRedirect(request, response, targetUrl);
    }

    /**
     * 從Authentication對象中提取當前登錄用戶的角色,並根據其角色返回適當的URL
     */
    protected String determineTargetUrl(Authentication authentication) {
        String url = "";
        // 獲取當前登錄用戶的角色權限集合
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        List<String> roles = new ArrayList<>();
        // 將角色名稱添加到List集合
        for (GrantedAuthority a : authorities) {
            roles.add(a.getAuthority());
        }
        // 判斷不同角色跳轉到不同的URL
        if (isAdmin(roles)) {
            url = "/admin";
        } else if (isUser(roles)) {
            url = "/home";
        } else {
            url = "/accessDenied";
        }
        System.out.println("url=" + url);
        return url;
    }

    private boolean isUser(List<String> roles) {
        if (roles.contains("ROLE_USER")) {
            return true;
        }
        return false;
    }

    private boolean isAdmin(List<String> roles) {
        if (roles.contains("ROLE_ADMIN")) {
            return true;
        }
        return false;
    }

    public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
        this.redirectStrategy = redirectStrategy;
    }

    public RedirectStrategy getRedirectStrategy() {
        return redirectStrategy;
    }
}

  1. 轉發請求的控制器
    AppController是一個Spring MVC的控制器,提供了響應login、home、admin、dba、accessDenied和logout請求的方法。每個方法通過getUsername()方法獲得當前認證用戶的用戶名。通過getAuthority()方法獲得當前認證用戶的權限,並設置到Model當中。
    在getUsername()方法、getAuthority()方法和logoutPage()方法中都使用了Authentication對象。Authentication是一個接口,用來表示用戶認證信息,在用戶登錄認證之後,相關信息會封裝爲一個Authentication具體實現類的對象,在登錄成功之後又會生成一個信息更全面、包含用戶權限等信息的Authentication對象,然後把它保存在SecurityContextHolder所持有的SecurityContext中,供後續的程序進行調用,如訪問權限的鑑定等。
  2. 測試應用
    運行main方法啓動項目,觀察控制檯,發現自定義類AppSecurityConfigurer的兩個方法都已經被執行,說明自定義的用戶認證和用戶授權工作已經生效。
    用戶認證和用戶授權已經生效
    在瀏覽器中輸入URL測試應用:
    登錄頁面
    注意:該項目存在問題。問題詳情參考:https://xinzhe.blog.csdn.net/article/details/105037639
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章