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的两个方法都已经被执行,说明自定义的用户认证和用户授权工作已经生效。