背景

Spring Security默认使用「用户名/密码」的方式进行登陆校验,并通过cookie的方式存留登陆信息。在一些定制化场景,比如希望单独使用token串进行部分页面的访问权限控制时,默认方案无法支持。在未能在网上搜索出相关实践的情况下,通过官方文档及个别Stack Overflow的零散案例,形成整体思路并实践测试通过,本文即关于该方案的一个分享。

参考

官方文档:https://docs.spring.io/spring-security/site/docs/5.0.5.BUILD-SNAPSHOT/reference/htmlsingle/

SpringSecurity校验流程

基本的SpringSecurity使用方式网上很多,不是本文关注的重点,如有需要可以自行搜索或参见https://blog.csdn.net/u012702547/article/details/54319508

关于校验的整个流程简单的说,整个链路有三个关键点,

  • 将需要鉴权的类/方法/url),定义为需要鉴权(本文代码示例为方法上注解@PreAuthorize("hasPermission('TARGET','PERMISSION')")
  • 根据访问的信息产生一个来访者的权限信息Authentication,并插入到上下文中
  • 在调用鉴权方法时,根据指定的鉴权方式,验证权限信息是否符合权限要求

完整的调用链建议在IDE中通过单步调试亲自体会,本文不做相关整理。

如何自定义

我的需求,是使用自定义的token,验证权限,涉及到:

  • 产生Authentication并插入到上下文中
  • 针对token的验证方式

需要做的事情如下:

  • 自定义TokenAuthentication类,实现org.springframework.security.core.Authenticaion,作为token权限信息
  • 自定义AuthenticationTokenFilter类,实现javax.servlet.Filter,在收到访问时,根据访问信息生成TokenAuthentication实例,并插入上下文
  • 自定义SecurityPermissionEvalutor类,实现org.springframework.security.access.PermissionEvaluator,完成权限的自定义验证逻辑
  • 在全局的配置中,定义使用SecurityPermissionEvalutor作为权限校验方式

TokenAuthentication.java

  1. /**
  2. * @author: Blaketairan
  3. */
  4. import org.springframework.security.core.Authentication;
  5. import org.springframework.security.core.GrantedAuthority;
  6. import java.util.ArrayList;
  7. import java.util.Collection;
  8. /**
  9. * Description: spring-security的Authentication的自定义实现(用于校验token)
  10. */
  11. public class TokenAuthentication implements Authentication{
  12. private String token;
  13. public TokenAuthentication(String token){
  14. this.token = token;
  15. }
  16. @Override
  17. public Collection<? extends GrantedAuthority> getAuthorities() {
  18. return new ArrayList<GrantedAuthority>(0);
  19. }
  20. @Override
  21. public Object getCredentials(){
  22. return token;
  23. }
  24. @Override
  25. public Object getDetails() {
  26. return null;
  27. }
  28. @Override
  29. public Object getPrincipal() {
  30. return null;
  31. }
  32. @Override
  33. public boolean isAuthenticated() {
  34. return true;
  35. }
  36. @Override
  37. public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
  38. }
  39. @Override
  40. public String getName() {
  41. return null;
  42. }
  43. }

AuthenticationTokenFilter.java

  1. /**
  2. * @author: Blaketairan
  3. */
  4. import com.google.common.base.Strings;
  5. import com.blaketairan.spring.security.configuration.TokenAuthentication;
  6. import org.springframework.context.annotation.Configuration;
  7. import org.springframework.security.core.Authentication;
  8. import org.springframework.security.core.context.SecurityContextHolder;
  9. import javax.servlet.*;
  10. import javax.servlet.http.HttpServletRequest;
  11. import java.io.IOException;
  12. /**
  13. * Description: 用于处理收到的token并为spring-security上下文生成及注入Authenticaion实例
  14. */
  15. @Configuration
  16. public class AuthenticationTokenFilter implements Filter{
  17. @Override
  18. public void init(FilterConfig filterConfig) throws ServletException{
  19. }
  20. @Override
  21. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,FilterChain filterChain)
  22. throws IOException, ServletException{
  23. if (servletRequest instanceof HttpServletRequest){
  24. String token = ((HttpServletRequest) servletRequest).getHeader("PRIVATE-TOKEN");
  25. if (!Strings.isNullOrEmpty(token)){
  26. Authentication authentication = new TokenAuthentication(token);
  27. SecurityContextHolder.getContext().setAuthentication(authentication);
  28. System.out.println("Set authentication with non-empty token");
  29. } else {
  30. /**
  31. * 在未收到Token时,至少塞入空TokenAuthenticaion实例,避免进入SpringSecurity的用户名密码默认模式
  32. */
  33. Authentication authentication = new TokenAuthentication("");
  34. SecurityContextHolder.getContext().setAuthentication(authentication);
  35. System.out.println("Set authentication with empty token");
  36. }
  37. }
  38. filterChain.doFilter(servletRequest, servletResponse);
  39. }
  40. @Override
  41. public void destroy(){
  42. }
  43. }

SecurityPermissionEvalutor.java

  1. /**
  2. * @author: Blaketairan
  3. */
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.security.access.PermissionEvaluator;
  6. import org.springframework.security.core.Authentication;
  7. import java.io.Serializable;
  8. /**
  9. * Description: spring-security 自定义的权限处理模块(鉴权)
  10. */
  11. public class SecurityPermissionEvaluator implements PermissionEvaluator {
  12. @Override
  13. public boolean hasPermission(Authentication authentication,Object targetDomainObject, Object permission){
  14. String targetDomainObjectString = null;
  15. String permissionString = null;
  16. String token = null;
  17. try {
  18. targetDomainObjectString = (String)targetDomainObject;
  19. permissionString = (String)permission;
  20. token = (String)authentication.getCredentials();
  21. } catch (ClassCastException e){
  22. e.printStackTrace();
  23. return false;
  24. }
  25. return hasPermission(token, targetDomainObjectString, permissionString);
  26. }
  27. @Override
  28. public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission){
  29. /**
  30. * 使用@PreAuthorize("hasPermission('TARGET','PERMISSION')")方式,不使用该鉴权逻辑
  31. */
  32. return false;
  33. }
  34. private boolean hasPermission(String token,String targetDomain, String permission){
  35. /**
  36. * 验证权限
  37. **/
  38. return true;
  39. }
  40. }

SecurityConfig.java 全局配置

  1. /**
  2. * @author: Blaketairan
  3. */
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.security.access.PermissionEvaluator;
  7. import org.springframework.security.authentication.AuthenticationManager;
  8. import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
  9. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  10. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
  11. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  12. /**
  13. * Description: spring-security配置,指定使用自定义的权限评估方法
  14. */
  15. @Configuration
  16. @EnableWebSecurity
  17. @EnableGlobalMethodSecurity(prePostEnabled = true)
  18. public class SecurityConfig extends WebSecurityConfigurerAdapter{
  19. @Bean
  20. @Override
  21. protected AuthenticationManager authenticationManager() throws Exception{
  22. return super.authenticationManager();
  23. }
  24. @Bean
  25. public PermissionEvaluator permissionEvaluator() {
  26. /**
  27. * 使用自定义的权限验证
  28. **/
  29. SecurityPermissionEvaluator securityPermissionEvaluator = new SecurityPermissionEvaluator();
  30. return securityPermissionEvaluator;
  31. }
  32. @Override
  33. protected void configure(HttpSecurity httpSecurity) throws Exception{
  34. /**
  35. * 关掉csrf方便本地ip调用调试
  36. **/
  37. httpSecurity
  38. .csrf()
  39. .disable()
  40. .httpBasic()
  41. .disable();
  42. }
  43. }

BaseRepository.java 某个需要权限验证的方法

  1. /**
  2. * @author: Blaketairan
  3. */
  4. import org.springframework.security.access.prepost.PreAuthorize;
  5. import java.util.List;
  6. /**
  7. * Description:
  8. */
  9. public interface BaseRepository{
  10. @PreAuthorize("hasPermission('DOMAIN', 'PERMISSION')")
  11. void deleteAll();
  12. }

结语

希望对看到本文的人有所帮助。

SpringSecurity 进行自定义Token校验的更多相关文章

  1. Spring Cloud Gateway 实现Token校验

    在我看来,在某些场景下,网关就像是一个公共方法,把项目中的都要用到的一些功能提出来,抽象成一个服务.比如,我们可以在业务网关上做日志收集.Token校验等等,当然这么理解很狭隘,因为网关的能力远不止如 ...

  2. django-jwt token校验源码简析

    一. jwt token校验源码简析 1.1 前言 之前使用jwt签发了token,里面的头部包含了加密的方式.是否有签名等,而载荷中包含用户名.用户主键.过期时间等信息,最后的签名还使用了摘要算法进 ...

  3. 待实践二:MVC3下的3种验证 (1)前台 jquery validate验证 (2)MVC实体验证 (3)EF生成的/自己手写的 自定义实体校验(伙伴类+元素据共享)

    MVC3下的3种验证 (1):前台Jquery Validate脚本验证 引入脚本 <script src="../js/jquery.js" type="text ...

  4. spring cloud 服务A调用服务B自定义token消失,记录

    后端:spring cloud 前端:vue 场景:前端ajax请求,包装自定义请求头token到后台做验证,首先调用A服务,A服务通过Feign调用B服务发现自定义token没有传到B服务去; 原因 ...

  5. Java自定义注解源码+原理解释(使用Java自定义注解校验bean传入参数合法性)

    Java自定义注解源码+原理解释(使用Java自定义注解校验bean传入参数合法性) 前言:由于前段时间忙于写接口,在接口中需要做很多的参数校验,本着简洁.高效的原则,便写了这个小工具供自己使用(内容 ...

  6. 自定义token,保存到客户端的cookie中,

    自定义token #原理自定义token,放入cookie中,不用存数据库 #token定义方式 >>>>> "加密字符串"|登陆用户id|用户登陆时 ...

  7. Struts2 自定义输入校验 第五弹

    Struts2的校验框架有两种:一种是validate方法,另一种是有效的xml文件. Action中自定义方法的输入校验,对于通过action的method属性所指定的自定义方法myExecute, ...

  8. Java 自定义注解 校验指定字段对应数据库内容重复

    一.前言 在项目中,某些情景下我们需要验证编码是否重复,账号是否重复,身份证号是否重复等... 而像验证这类代码如下: 那么有没有办法可以解决这类似的重复代码量呢? 我们可以通过自定义注解校验的方式去 ...

  9. 更加灵活的参数校验,Spring-boot自定义参数校验注解

    上文我们讨论了如何使用@Min.@Max等注解进行参数校验,主要是针对基本数据类型和级联对象进行参数校验的演示,但是在实际中我们往往需要更为复杂的校验规则,比如注册用户的密码和确认密码进行校验,这个时 ...

随机推荐

  1. POJ-1122 FDNY to the Rescue!---Dijkstra+反向建图

    题目链接: https://vjudge.net/problem/POJ-1122 题目大意: 给出矩阵,矩阵中每个元素tij表示从第i个交叉路口到第j个交叉路口所需时间,若tij为-1则表示两交叉路 ...

  2. [翻译] softmax和softmax_cross_entropy_with_logits的区别

    翻译自:https://stackoverflow.com/questions/34240703/whats-the-difference-between-softmax-and-softmax-cr ...

  3. 04、NetCore2.0下Web应用之Startup源码解析

    04.NetCore2.0Web应用之Startup源码解析   通过分析Asp.Net Core 2.0的Startup部分源码,来理解插件框架的运行机制,以及掌握Startup注册的最优姿势. - ...

  4. Azure AI 服务之文本翻译

    当下人工智能可谓是风头正劲,几乎所有的大厂都有相关的技术栈.微软在 AI 领域自然也是投入了重注,并且以 Azure 认知服务的方式投入了市场: 也就是说作为开发者我们不需要学习太多 AI 的理论知识 ...

  5. 关于阮大神的es6标准入门第一章

    题记:之前在10月份的时候写过阮大神的es6的第一章,但是由于那段时间项目组的动荡,所以也没有什么后续,导致我现在对es6基本都忘的差不多了,不过,现在换了新公司,最近也没什么任务,所以现在开始重新写 ...

  6. ES6 new syntax of let and const (one)

    variable declarations : let, const,and block scope why we redefine the way about declarations? funct ...

  7. [LeetCode] My Calendar I 我的日历之一

    Implement a MyCalendar class to store your events. A new event can be added if adding the event will ...

  8. [LeetCode] Minimum Index Sum of Two Lists 两个表单的最小坐标和

    Suppose Andy and Doris want to choose a restaurant for dinner, and they both have a list of favorite ...

  9. [SDOI2011]黑白棋

    Description 小A和小B又想到了一个新的游戏. 这个游戏是在一个1*n的棋盘上进行的,棋盘上有k个棋子,一半是黑色,一半是白色. 最左边是白色棋子,最右边是黑色棋子,相邻的棋子颜色不同. 小 ...

  10. ●BZOJ 2005 NOI 2010 能量采集

    题链: http://www.lydsy.com/JudgeOnline/problem.php?id=2005 题解: 一个带有容斥思想的递推.%%% 首先,对于一个点 (x,y) 在路径 (0,0 ...