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。
- Authentication: 用來表示用戶認證的信息,在用戶登錄認證之前,Spring Security會將相關信息封裝爲一個Authentication具體實現類的對象,在登錄認證成功之後又會生成一個信息更全面,包含用戶權限等信息的Authentication對象,然後把它保存在SecurityContextHolder所持有的SecurityContext中,供後續的程序進行調用,如訪問權限的鑑定等。
- 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();
- 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即可。 - 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。 - GrantedAuthority
Authentication的getAuthorities()可以返回當前Authentication對象擁有的權限,即當前用戶擁有的權限。其返回值是一個GrantedAuthority類型的數組,每一個GrantedAuthority對象代表賦予給當前用戶的一種權限。GrantedAuthority是一個接口,其通常是通過UserDetailsService進行加載,然後賦予給UserDetails的。
GrantedAuthority中只定義了一個getAuthority()方法,該方法返回一個字符串,表示對應權限的字符串表示,如果對應權限不能用字符串表示,則應當返回null。
Spring Security針對GrantedAuthority有一個簡單實現SimpleGrantedAuthority。該類只是簡單的接收一個表示權限的字符串。Spring Security內部的所有AuthenticationProvider都是使用SimpleGrantedAuthority來封裝Authentication對象。 - DaoAuthenticationProvider
Spring Security默認會使用DaoAuthenticationProvider實現AuthenticationProvider接口,專門進行用戶認證的處理。DaoAuthenticationProvider在進行認證的時候需要一個UserDetailsService來獲取用戶的信息UserDetails,其中包括用戶名、密碼和所擁有的權限等。如果需要改變認證的方式,開發者可以實現自己的AuthenticationProvider。 - 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兩個文件夾。
- 修改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>
- 開發用於測試的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>
- 創建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");
}
}
- 創建認證成功處理類
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;
}
}
- 轉發請求的控制器
AppController是一個Spring MVC的控制器,提供了響應login、home、admin、dba、accessDenied和logout請求的方法。每個方法通過getUsername()方法獲得當前認證用戶的用戶名。通過getAuthority()方法獲得當前認證用戶的權限,並設置到Model當中。
在getUsername()方法、getAuthority()方法和logoutPage()方法中都使用了Authentication對象。Authentication是一個接口,用來表示用戶認證信息,在用戶登錄認證之後,相關信息會封裝爲一個Authentication具體實現類的對象,在登錄成功之後又會生成一個信息更全面、包含用戶權限等信息的Authentication對象,然後把它保存在SecurityContextHolder所持有的SecurityContext中,供後續的程序進行調用,如訪問權限的鑑定等。 - 測試應用
運行main方法啓動項目,觀察控制檯,發現自定義類AppSecurityConfigurer的兩個方法都已經被執行,說明自定義的用戶認證和用戶授權工作已經生效。
在瀏覽器中輸入URL測試應用:
注意:該項目存在問題。問題詳情參考:https://xinzhe.blog.csdn.net/article/details/105037639