SpringSecurity
第一节 SpringSecurity 简介
1.1 概念
Spring Security is a powerful and highly customizable authentication and access-control framework.
Spring Security 是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在 Spring 应用上下文中配置的 Bean,充分利用了 Spring IoC,DI 和 AOP 功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。 Spring Security 的主要核心功能为 认证和授权,整个架构也是基于这两个核心功能去实现的。
1.2 原理
众所周知 想要对 Web 资源进行保护,最好的办法莫过于 Filter 和 Interceptor。
所以 SpringSecurity 在我们进行用户认证以及授予权限的时候,通过各种各样的拦截器来控制权限的访问,从而实现安全。 如下为其主要过滤器 :
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CorsFilter
LogoutFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
UsernamePasswordAuthenticationFilter
BasicAuthenticationFilter
1.3 核心组件简介
Spring Security bean 的核心类
SecurityContextHolder:提供对 SecurityContext 的访问
SecurityContext,:持有 Authentication 对象和其他可能需要的信息
AuthenticationManager: 其中可以包含多个 AuthenticationProvider
ProviderManager:对象为 AuthenticationManager 接口的实现类
AuthenticationProvider: 主要用来进行认证操作的类 调用其中的 authenticate()方法去进行认证操作
Authentication:Spring Security 方式的认证主体
GrantedAuthority:对认证主题的应用层面的授权,含当前用户的权限信息,
通常使用角色表示
UserDetails:构建 Authentication 对象必须的信息,可以自定义,可能需要
访问 DB 得到
UserDetailsService:通过 username 构建 UserDetails 对象,通过 loadUserByUsername 根据 userName 获取 UserDetail 对象 (可以在这里基
于自身业务进行自定义的实现 如通过数据库,xml,缓存获取等)
1.4 验证和授权的过程及本质
用户一次完整的登录验证和授权,是一个请求经过 层层拦截器从而实现权
限控制,整个 web 端配置为 DelegatingFilterProxy,它并不实现真正的过滤,
而是所有过滤器链的代理类,真正执行拦截处理的是由 spring 容器管理的各个
filter bean 组成的 filterChain。
第二节 简单应用
2.1 引入依赖
版本号可以不加
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>2.6.9</version> </dependency> |
2.2 访问页面
导入 spring-boot-starter-security 启动器后,Spring Security 已经生效,默
认拦截全部请求,如果用户没有登录,跳转到内置登录页面。
在resources下建立static目录,然后再static目录下创建index.html
在浏览器输入:http://localhost:8080后会显示下面页面
默认的 username 为 user,password 打印在控制台中。以上为默认值。
在浏览器中输入账号和密码后登录成功后会显示 index.html 页面内容。
第三节 认证相关 API
3.1 UserDetailsService 详解
当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。所以我们要通过自定义逻辑控制认证逻辑。如果需要自定义逻辑时,只需要实现 UserDetailsService 接口即可。接口定义如下:
该接口抽象方法的返回值 UserDetails 是一个接口,定义如下:
要想返回 UserDetails 的实例就只能返回接口的实现类。SpringSecurity 中提供了如下的实例。对于我们只需要使用里面的 User 类即可。
注意 User 的全限定路径是:
org.springframework.security.core.userdetails.User 此处经常和系统中自己开发的 User 类弄混。
在 User 类中提供了很多方法和属性:
构造方法参数说明:
username:用户名
password:密码
authorities:用户具有的权限。此处不允许为 null
此处的用户名应该是客户端传递过来的用户名。而密码应该是从数据库中查询出来的密码。Spring Security 会根据 User 中的 password 和客户端传递过来的 password 进行比较。如果相同则表示认证通过,如果不相同表示认证失败。 authorities 里面的权限对于后面学习授权是很有必要的,包含的所有内容为此用户具有的权限,如有里面没有包含某个权限,而在做某个事情时必须包含某个权限则会出现 403 。通常都是通过
AuthorityUtils.commaSeparatedStringToAuthorityList(“”) 来创建 authorities 集合对象的。参数是一个字符串,多个权限使用逗号分隔。
注意:
username 是客户端表单传递过来的数据。默认情况下参数名必须 username,否则无法接收。
3.2 PasswordEncoder 密码编码器接口详解
- SpringSecurity 要求容器中必须有 PasswordEncoder 实例。
- 所以当自定义登录逻辑时要求必须给容器注入PaswordEncoder 的 bean 对象。
接口介绍:
- encode():把参数按照特定的解析规则进行解析。
- matches()验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹配,则返回 true;如果不匹配,则返回 false。第一个参数表示需要被解析的密码。第二个参数表示存储的密码。
- upgradeEncoding():如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回 false。默认返回 false。
- PaswordEncoder 实现类有很多:
比较常用的BCryptPasswordEncoder
- BCryptPasswordEncoder 是 Spring Security 官方推荐的密码编码器,平时多使用这个编码器。
- BCryptPasswordEncoder 是对 bcrypt 强散列方法的具体实现。是基于 Hash算法实现的单向加密。可以通过 strength 控制加密强度,默认 10.
示例代码:
package com.tjetc;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@SpringBootTest
class SpringbootSecurityApplicationTests {
@Test
public void testEncoder() {
/*
1、创建解析器 2、对原始密码加密 3、判断原字符加密后和内容是否匹配
*/
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String encode = encoder.encode("175269588");
System.out.println("175269588加密后的密码:" + encode);
boolean matches = encoder.matches("111", encode);
boolean matches1 = encoder.matches("175269588", encode);
System.out.println("是否与原密码匹配:" + matches);
System.out.println("是否与原密码匹配:" + matches1);
}
}
![]() |
3.3 自定义认证逻辑
3.3.1 编写配置类
package com.tjetc.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder getPwdEncoder() {
return new BCryptPasswordEncoder();
}
}
3.3.2 编写认证服务实现类
该类需要实现 UserDetailsService 接口
public User(String username, String password, Collection<? extends GrantedAuthority> authorities):从数据中查询用户的权限信息 AuthorityUtils.commaSeparatedStringToAuthorityList:把用逗号分割的权限字符串转换成权限对象的集合 |
package com.tjetc.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder encoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
/*
1. 查询数据库判断用户名是否存在,如果不存在抛出相应异常
2. 把查询出来的正确密码进行解析,或直接把 password 放到构造方法中
3. 查询用户权限信息,返回UserDetails实现类对象
*/
if (!username.equals("admin")) {
throw new UsernameNotFoundException("用户名不存在");
}
String password = encoder.encode("123");
/*public User(String username, String password, Collection<? extends GrantedAuthority> authorities)*/
return new User(username, password,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
3.3.3 测试
重启项目后,在浏览器中输入账号:admin,密码:123 则能进入到 index.html 页面,否则登录失败。
3.4 自定义登录页面
虽然 Spring Security 给我们提供了登录页面,但是对于实际项目中,大多喜欢使用自己的登录页面。所以 Spring Security 中不仅仅提供了登录页面,还支持用户自定义登录页面。实现过程也比较简单,只需要修改配置类即可。
3.4.1 编写登录页面
在static目录下创建login.html登录页面,在页面中<form>的 action 不编写对应控制器也可以。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form action="/login" method="post">
用户名:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/><br/>
<input type="submit" value="登录"/>
</form>
</body>
</html>
3.4.2 修改配置类
写配置类中主要是设置哪个页面是登录页面。配置类需要继承
WebSecurityConfigurerAdapter,并重写configure 方法。
successForwardUrl()登录成功后跳转地址 loginPage() 登录页面 loginProcessingUrl 登录页面表单提交地址,此地址可以不真实存在。
antMatchers()匹配内容permitAll()允许
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置表单认证
http.formLogin()
.loginProcessingUrl("/login")//配置处理登录的URL
.successForwardUrl("/toMain")//登录成功之后的跳转
.failureForwardUrl("/fail")//失败
.loginPage("/login.html");//登录页面
//URL拦截(权限)
http.authorizeHttpRequests()
.antMatchers("/login.html")//匹配的url 不要登录就能访问
.permitAll()//允许上述的antMatchers 匹配的url 直接访问,不要登录
.anyRequest()//其他的任何请求
.authenticated();//其他anyRequest所有的请求都必须被认证。必须登录后才能访问
//关闭CSRF
http.csrf().disable();
}
}
3.4.3 编写控制器
@Controller
public class LoginController {
@RequestMapping("toMain")
public String toMain() {
return "redirect:/main.html";//重定向
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>toMain</title>
</head>
<body>
<h1>登录成功之后的跳转的页面</h1>
</body>
</html>
3.5 认证过程其他常用配置
3.5.1 失败跳转
表单处理中成功会跳转到一个地址,失败也可以跳转到一个地址中。
- 在 static 目录下编写失败页面
- 修改表单配置在配置方法中表单认证部分添加failureForwardUrl()方法,表示
登
录失败跳转的url。此处依然是POST 请求,所以跳转到可以接收 POST请求的控制器/fail 中。
//配置表单认证
http.formLogin()
.loginProcessingUrl("/login")//当发现/login 时认为是登录,需要执行 UserDetailsServiceImpl
.successForwardUrl("/toMain")
.failureForwardUrl("/fail")//失败
.loginPage("/login.html")//登录页面
- 添加控制器方法
@RequestMapping("/fail")
public String fail(){
return "redirect:/fail.html";
}
- 设置html 不需要认证
//URL拦截(权限)
http.authorizeHttpRequests()
.antMatchers("/login.html", "/fail.html")//匹配的url 不要登录就能访问
.permitAll()//允许上述的antMatchers 匹配的url 直接访问,不要登录
.anyRequest()//其他的任何请求
.authenticated();//其他anyRequest所有的请求都必须被认证。必须登录后才能访问
3.5.2 自定义请求中的用户名和密码
当进行登录时会执行 UsernamePasswordAuthenticationFilter 过滤器。
通过查看源码知道,参数名默认必须为username和password,并且只接收post
请求。那如何更改默认的参数名呢?
- 修改配置对象
//配置表单认证
http.formLogin()
.loginProcessingUrl("/login")//当发现/login 时认为是登录,需要执行 UserDetailsServiceImpl
.successForwardUrl("/toMain")//登录成功之后的跳转
.failureForwardUrl("/fail")//失败
.loginPage("/login.html")//登录页面
.usernameParameter("myUsername")
.passwordParameter("myPassword");
配置好后,页面就可以根据配置进行自定义参数名了
<form action="/login" method="post">
用户名:<input type="text" name="myUsername" autocomplete="off"/><br/>
密码:<input type="password" name="myPassword"/><br/>
<input type="submit" value="登录"/>
</form>
3.5.3 自定义登录成功处理器
使用JsonResult
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.74</version>
</dependency>
package com.tjetc.common;
public class JsonResult<T> {
//0 代表成功,1代表失败,-1代表登录过期
private int state;
//失败的提示信息
private String message;
//返回的数据
private T data;
public JsonResult(int state, String message, T data) {
this.state = state;
this.message = message;
this.data = data;
}
public int getState() {return state; }
public String getMessage() {return message; }
public T getData() { return data;}
}
1.源码分析使用 successForwardUrl()时表示成功后转发请求到地址。内部是通过 successHandler()方法进行控制成功后交给哪个类进行处理。
ForwardAuthenticationSuccessHandler 内部就是最简单的请求转
发。由于是请求转发,当遇到需要跳转到站外或在前后端分离的项目中就无法使用了。
如何自定义成功处理器来实现重定向跳转呢?
- 代码示例
- 编写一个控制器类实现 AuthenticationSuccessHandler 接口
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
response.setContentType("text/html;charset=utf-8");
//Principal 主体,存放了登录用户的信息
User user = (User) authentication.getPrincipal();
System.out.println(user);
JsonResult<String> result = new JsonResult<>(0, "登录成功", user.getUsername());
String jsonString = JSONObject.toJSONString(result);
System.out.println(jsonString);
response.getWriter().write(jsonString);
}
}
- 修改配置类使用 successHandler()方法设置成功后交给哪个对象进行处理
//配置表单认证
http.formLogin()
.loginProcessingUrl("/login")//配置处理登录的URL
/* .successForwardUrl("/toMain")//登录成功之后的跳转*/
.successHandler(new MyAuthenticationSuccessHandler())
.failureForwardUrl("/fail")//失败
.loginPage("/login.html");//登录页面
3.5.4 自定义登录失败处理器
同理,能否自定义一个可以重定向的登录失败处理器呢?
- 1.源码分析
failureForwardUrl()内部调用的是 failureHandler()方法
ForwardAuthenticationFailureHandler 中 也 是 一 个 请 求 转 发 , 并 在 request 作用域中设置 SPRING_SECURITY_LAST_EXCEPTION 的 key,内
容为异常对象。
- 2.代码示例
- 编写一个控制器类实现 AuthenticationFailureHandler 接口
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setContentType("text/html;charset=utf-8");
JsonResult<String> result = new JsonResult<>(1, exception.getMessage(), null);
String jsonString = JSONObject.toJSONString(result);
System.out.println(jsonString);
response.getWriter().write(jsonString);
}
}
- 修改配置类
//配置表单认证
http.formLogin()
.loginProcessingUrl("/login")//当发现/login 时认为是登录,需要执行 UserDetailsServiceImpl
/* .successForwardUrl("/toMain")//登录成功之后的跳转*/
.successHandler(new MyAuthenticationSuccessHandler())
/*.failureForwardUrl("/fail")//失败*/
.failureHandler(new MyAuthenticationFailureHandler())
.loginPage("/login.html");//登录页面
第四节 授权相关 API
4.1 url 匹配规则详解
在前面讲解了认证中所有常用配置,主要是对 http.formLogin()进行操作。而在配置类中 http.authorizeRequests()主要是对 url 进行控制,也就是我们所说的授权(访问控制)。http.authorizeRequests()也支持连缀写法,总体格式为: url 匹配规则.权限控制方法
通过上面的格式可以有很多 url 匹配规则和很多权限控制方法。这些内容进行各种组合就形成了 Spring Security 中的授权。
在所有匹配规则中取所有规则的交集。配置顺序影响了之后授权效果,越是具体的应该放在前面,越是笼统的应该放到后面。
4.1.1 anyRequest()
在之前认证过程中我们就已经使用过 anyRequest(),表示匹配所有的请求。一般情况下此方法都会使用,设置全部内容都需要进行认证。
代码示例:
anyRequest().authenticated();
4.1.2 antMatcher()
方法定义如下:
public C antMatchers(String... antPatterns)
参数是不定向参数,每个参数是一个 ant 表达式,用于匹配 URL 规则。规则如下:
- ? 匹配一个字符
- * 匹配 0 个或多个字符 ** 匹配 0 个或多个目录在实际项目中经常需要放行所有静态资源
下面演示放行 js 文件夹下所有脚本文件。
.antMatchers("/js/**").permitAll()
还有一种配置方式是只要是.js 文件都放行
.antMatchers("/**/*.js").permitAll()
4.2 权限控制方法详解
Spring Security 匹配了URL 后调用了permitAll()表示无需授权,随意访问。在Spring Security 中提供了多种内置控制。
4.2.1 permitAll()
permitAll()表示所匹配的URL任何人都允许访问。
4.2.2 authenticated()
authenticated()表示所匹配的URL需要被认证才能访问。
4.2.3 anonymous()
anonymous()表示可以匿名访问匹配的URL。和permitAll()效果类似,对于匿名访问的用户,Spring Security支持为其建立一个匿名
AnonymousAuthenticationToken存放在SecurityContextHolder中,这就是
所谓的匿名认证。这样在以后进行权限认证或者做其它操作时我们就不需要再判
断SecurityContextHolder中持有的Authentication对象是否为null了,而直接把它当做一个正常的Authentication进行使用就OK了。
4.2.4 denyAll()
denyAll()表示所匹配的URL 都不允许被访问。
4.2.5 rememberMe()
被“remember me”的用户允许访问。
4.2.5 fullyAuthenticated()
用户通过正常登录认证的而不是被remember me 的,才可以访问
4.3 角色权限判断
4.3.1 hasAuthority(String)
判断用户是否具有特定的权限,用户的权限是在自定义登录逻辑中创建 User 对象时指定的。下图中 admin 就是用户的权限。admin 严格区分大小写。
在配置类中通过 hasAuthority(“admin”)设置具有 admin 权限时才能访问。
.antMatchers("/main1.html").hasAuthority("admin") |
admin登录成功能访问 |
.antMatchers("/main1.html").hasAuthority("admin1") admin登录成功不能访问 |
4.3.2 hasAnyAuthority(String ...)
如果用户具备给定权限中某一个,就允许访问。
.antMatchers("/main1.html").hasAnyAuthority("admin","admin1","admin2")
4.3.3 hasRole(String)
如果用户具备给定角色就允许访问。否则出现 403。
参数取值来源于自定义登录逻辑 UserDetailsService 实现类中创建 User 对象时给 User 赋予的授权。
在给用户赋予角色时角色需要以:ROLE_ 开头,后面添加角色名称。
例如:ROLE_abc 其中 abc 是角色名,ROLE_是固定的字符开头。
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder encoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (!username.equals("admin")) {
throw new UsernameNotFoundException("用户名不存在");
}
String password = encoder.encode("123");
return new User(username, password,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_abc"));
}
}
使用 hasRole()时参数也只写 abc 即可。
.antMatchers("/main2.html")
.hasRole("abc")
否则启动报错。
.antMatchers("/main2.html")
.hasRole("abc1")
4.3.4 hasAnyRole(String ...)
如果用户具备给定角色的任意一个,就允许被访问,否则403
.antMatchers("/main2.html").hasAnyRole("abc","abc1","abc2")
4.3.5 hasIpAddress(String)
如果请求是指定的 IP 就允许被访问。
可以通过 request.getRemoteAddr()获取 ip 地址。
.antMatchers("/test")
@Controller
public class LoginController {
@RequestMapping("/test")
@ResponseBody
public String test(HttpServletRequest request){
return request.getRemoteAddr();
}
}
需要注意的是在本机进行测试时 localhost 和 127.0.0.1 输出的 ip 地址是不一样的。
当浏览器中通过 localhost 进行访问:
当浏览器中通过 127.0.0.1 访问:
当浏览器中通过具体 ip 进行访问:
http.authorizeRequests()
//IP
.antMatchers("/main3.html").hasIpAddress("127.0.0.1")
4.4 自定义无权限 403 处理方案
使用 Spring Security 时经常会看见 403(无权限),默认情况下显示的效果如下:
显示上述效果对于用户就不是特别友好了。Spring Security 支持自定义权限受限页面内容。
4.4.1 新建处理器类
实现 AccessDeniedHandler 接口
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
System.out.println(accessDeniedException);
//设置响应状态码 403
response.setStatus(response.SC_FORBIDDEN);
response.setHeader("Content-Type", "application/json;charset=utf-8");
/*response.setContentType("text/html;charset=utf-8");*/
String jsonString = JSONObject.toJSONString(new JsonResult<>(2, "权限不足", null));
PrintWriter writer = response.getWriter();
writer.write(jsonString);
writer.flush();
writer.close();
}
}
4.4.2 修改配置类
配置类中重点添加异常处理器。设置访问受限后交给哪个对象进行处理。
myAccessDeniedHandler 是在配置类中进行自动注入的。
//异常处理
http.exceptionHandling()
.accessDeniedHandler(new MyAccessDeniedHandler());
4.5 基于表达式的访问控制
4.5.1 使用 access()方法内置表达式
之前学习的登录用户权限判断实际上底层实现都是调用access(表达式), 可以通过在 access()中添加表达式实现和之前学习的权限控制完成相同的功能。常用表达式如下:
.access("permitAll()") 等价于 .permitAll() |
.access("hasAnyAuthority('admin1','admin2')")等价于 .hasAnyAuthority("admin1","admin2") |
注意:access 中的 hasRole 角色名称前一定不要添加“ROLE_”
4.5.2 使用自定义方法
虽然这里面已经包含了很多的表达式(方法)但是在实际项目中很有可能出现需要自己自定义逻辑的情况。
例如,判断登录用户是否具有访问当前 URL 权限。
隐含条件:用户必须登录
- 编写 service 接口及实现类
public interface MyService {
boolean hasPermission(HttpServletRequest request, Authentication authentication);
}
@Service
public class MyServiceImpl implements MyService {
@Override
public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
//获取登录的用户信息
Object principal = authentication.getPrincipal();
//true:说明能够获取用户对象,那么说明用户已经登录过了
if (principal != null) {
if (principal instanceof UserDetails) {//true:principal是UserDetails类型
UserDetails user = (UserDetails) principal;
//获取的登录用户所有权限集合
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
/*
String getRequestURI();//获取请求的URL
public SimpleGrantedAuthority(String role){}//URL转换成GrantedAuthority对象
boolean contains(Object o);//判断用户所有权限集合是否包含请求的uri对应的权限对象
*/
return authorities.contains(new SimpleGrantedAuthority(request.getRequestURI()));
} else {
return false;//无权限
}
} else {//用户没登录 无权限
return false;
}
}
}
- 修改配置类在 access 中通过@bean 的 id 名.方法(参数)的形式进行调用自定义的方法
//URL拦截(权限)
http.authorizeRequests()
.antMatchers("/login.html", "/fail.html", "/js/**", "/**/*.css", "/test")//匹配的url 不要登录就能访问
.access("permitAll()")
.anyRequest()//其他的任何请求
.access("@myServiceImpl.hasPermission(request,authentication)");
- 运行效果
未登录,直接访问main1.html,跳转到登录页面
登录后访问main1.html
4.6 基于注解的访问控制
在 Spring Security 中提供了一些访问控制的注解。这些注解都是默认是都不可用的,需要通过在启动类上添加@EnableGlobalMethodSecurity 进行开启后使用,并根据功能的不同,在该注解中添加不同的属性。
这 些 注 解 可 以 写 到 Service 接 口 或 方 法 上 上 也 可 以 写 到 Controller 或 Controller 的方法上。通常情况下都是写在控制器方法上的,控制接口 URL 是否允许被访问。
@Controller
public class LoginController {
@Secured("ROLE_abc")
@RequestMapping("toMain")
public String toMain() {
return "redirect:/main.html";//重定向
}
@RequestMapping("/fail")
public String fail(){
return "redirect:/fail.html";
}
}
@Controller
public class LoginController {
@Secured("ROLE_abc1")
@RequestMapping("toMain")
public String toMain() {
return "redirect:/main.html";//重定向
}
@RequestMapping("/fail")
public String fail(){
return "redirect:/fail.html";
}
}
如果没有授权,则报 500 异常
4.6.1 @Secured
@Secured 是专门用于判断是否具有角色的。能写在方法或类上。参数要以 ROLE_开头。
- 1. 修改启动类在启动类( 也可以在配置类等能够扫描的类上) 上添加
@EnableGlobalMethodSecurity(securedEnabled = true)
@EnableGlobalMethodSecurity(prePostEnabled = true)
@SpringBootApplication
public class SpringbootSecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootSecurityApplication.class, args);
}
}
- 在控制器方法上添加@Secured 注解该注解的值为 UserDetailServiceImpl 类中设置的角色名称
@Secured("ROLE_abc")
@RequestMapping("/toMain")
public String toMain() {
return "redirect:/main.html";//重定向
}
- 配置类
配置类中方法配置保留最基本的配置即可。
@Override
protected void configure(HttpSecurity http) throws Exception {
//配置表单认证
http.formLogin()
.loginProcessingUrl("/login")//当发现/login 时认为是登录,需要执行 UserDetailsServiceImpl
/* .successForwardUrl("/toMain")//登录成功之后的跳转*/
.successHandler(new MyAuthenticationSuccessHandler())
.loginPage("/login.html")//登录页面
.usernameParameter("myUsername")
.passwordParameter("myPassword");
//URL拦截(权限)
http.authorizeRequests()
.antMatchers("/login.html", "/fail.html", "/js/**", "/**/*.css", "/test")//匹配的url 不要登录就能访问
.access("permitAll()")
.anyRequest()//其他的任何请求
.authenticated();
//关闭CSRF
http.csrf().disable();
}
4.6.2 @PreAuthorize/@PostAuthorize
@PreAuthorize 和@PostAuthorize 都是方法或类级别注解。
@PreAuthorize 表示访问方法或类在执行之前先判断权限,大多情况下都是使用这个注解,注解的参数和 access()方法参数取值相同,都是权限表达式。
@PostAuthorize 表示方法或类执行结束后判断权限,此注解很少被使用到。
- 修改启动类
@EnableGlobalMethodSecurity(prePostEnabled = true)
@SpringBootApplication
public class SpringbootSecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootSecurityApplication.class, args);
}
}
4.6.3 添加@PreAuthorize
在控制器方法上添加@PreAuthorize,参数可以是任何 access()支持的表达式。注意:该注解中的 hasRole 角色名称前可以添加“ROLE_”(与 access 不同)
@PreAuthorize("hasAnyRole('ROLE_abc')")
@RequestMapping("/toMain")
public String toMain() {
return "redirect:/main.html";//重定向
}
第五节 其他功能
5.1 Remember Me 功能实现
Spring Security 中 Remember Me 为“记住我”功能,用户只需要在登录时添加 remember-me 复选框,取值为 true。Spring Security 会自动把用户信
息存储到数据源中,以后就可以不登录进行访问。
5.1.1 引入依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
5.1.2 修改核心属性文件添加数据源信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
5.1.3 编写配置类
package com.tjetc.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;
@Configuration
public class RememberMeConfig {
@Autowired
private DataSource dataSource;
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();
//设置数据源
repository.setDataSource(dataSource);
//自动建表存储 rememberme 信息,第一次启动时需要执行下面的代码,第二次启动时注释掉
/*repository.setCreateTableOnStartup(true);*/
return repository;
}
}
5.1.4 修改 SecurityConfig 配置类
//配置rememberMe
http.rememberMe()
/*@Autowired装配进来*/
.userDetailsService(userDetailsService) //登录逻辑交给哪个对象
.tokenRepository(repository); //持久层对象
5.1.5 在客户端页面中添加复选框
在客户端登录页面中添加 remember-me 的复选框,只要用户勾选了复选框下次就不需要进行登录了。注意:属性名默认必须为"remember-me"。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form action="/login" method="post">
用户名:<input type="text" name="myUsername" autocomplete="off"/><br/>
密码:<input type="password" name="myPassword"/><br/>
记住我:<input type="checkbox" name="remember-me" value="true"/> <br/>
<input type="submit" value="登录"/>
</form>
</body>
</html>
5.1.6 设置 rememberme 有效时间
有效时间默认为 2 周。
//配置rememberMe
http.rememberMe()
.tokenValiditySeconds(120)//单位:秒 免登录
/*@Autowired装配进来*/
.userDetailsService(userDetailsService) //登录逻辑交给哪个对象
.tokenRepository(repository); //持久层对象
登录后
5.1.7 运行效果
5.2 退出登录
用户只需要向 Spring Security 项目中发送/logout 退出请求即可。实现退出非
常简单,只要在页面中添加/logout 的超链接即可。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>toMain</title>
</head>
<body>
<h1>登录成功之后的跳转的页面</h1>
<a href="/logout">登出</a>
</body>
</html>
退出成功后指定配置路径,可以通过下面的方法进行修改。
//登出
http.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login.html");
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?