背景

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

/**
* @author: Blaketairan
*/ import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority; import java.util.ArrayList;
import java.util.Collection; /**
* Description: spring-security的Authentication的自定义实现(用于校验token)
*/
public class TokenAuthentication implements Authentication{
private String token;
public TokenAuthentication(String token){
this.token = token;
} @Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return new ArrayList<GrantedAuthority>(0);
} @Override
public Object getCredentials(){
return token;
} @Override
public Object getDetails() {
return null;
} @Override
public Object getPrincipal() {
return null;
} @Override
public boolean isAuthenticated() {
return true;
} @Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { } @Override
public String getName() {
return null;
}
}

AuthenticationTokenFilter.java

/**
* @author: Blaketairan
*/ import com.google.common.base.Strings;
import com.blaketairan.spring.security.configuration.TokenAuthentication;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException; /**
* Description: 用于处理收到的token并为spring-security上下文生成及注入Authenticaion实例
*/
@Configuration
public class AuthenticationTokenFilter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException{ } @Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,FilterChain filterChain)
throws IOException, ServletException{
if (servletRequest instanceof HttpServletRequest){
String token = ((HttpServletRequest) servletRequest).getHeader("PRIVATE-TOKEN");
if (!Strings.isNullOrEmpty(token)){
Authentication authentication = new TokenAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
System.out.println("Set authentication with non-empty token");
} else {
/**
* 在未收到Token时,至少塞入空TokenAuthenticaion实例,避免进入SpringSecurity的用户名密码默认模式
*/
Authentication authentication = new TokenAuthentication("");
SecurityContextHolder.getContext().setAuthentication(authentication);
System.out.println("Set authentication with empty token");
}
}
filterChain.doFilter(servletRequest, servletResponse);
} @Override
public void destroy(){
}
}

SecurityPermissionEvalutor.java

/**
* @author: Blaketairan
*/ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication; import java.io.Serializable; /**
* Description: spring-security 自定义的权限处理模块(鉴权)
*/
public class SecurityPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication authentication,Object targetDomainObject, Object permission){
String targetDomainObjectString = null;
String permissionString = null;
String token = null;
try {
targetDomainObjectString = (String)targetDomainObject;
permissionString = (String)permission;
token = (String)authentication.getCredentials();
} catch (ClassCastException e){
e.printStackTrace();
return false;
}
return hasPermission(token, targetDomainObjectString, permissionString);
} @Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission){
/**
* 使用@PreAuthorize("hasPermission('TARGET','PERMISSION')")方式,不使用该鉴权逻辑
*/
return false;
} private boolean hasPermission(String token,String targetDomain, String permission){
/**
* 验证权限
**/
return true;
}
}

SecurityConfig.java 全局配置

/**
* @author: Blaketairan
*/ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; /**
* Description: spring-security配置,指定使用自定义的权限评估方法
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception{
return super.authenticationManager();
} @Bean
public PermissionEvaluator permissionEvaluator() {
/**
* 使用自定义的权限验证
**/
SecurityPermissionEvaluator securityPermissionEvaluator = new SecurityPermissionEvaluator();
return securityPermissionEvaluator;
} @Override
protected void configure(HttpSecurity httpSecurity) throws Exception{
/**
* 关掉csrf方便本地ip调用调试
**/
httpSecurity
.csrf()
.disable()
.httpBasic()
.disable();
} }

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

/**
* @author: Blaketairan
*/ import org.springframework.security.access.prepost.PreAuthorize; import java.util.List; /**
* Description:
*/
public interface BaseRepository{
@PreAuthorize("hasPermission('DOMAIN', 'PERMISSION')")
void deleteAll();
}

结语

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

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. R语言学习 第九篇:plyr包

    在数据分析中,整理数据的本质可以归纳为:对数据进行分割(Split),然后应用(Apply)某些处理函数,最后将结果重新组合(Combine)成所需的格式返回,简单描述为:Split - Apply ...

  2. 记录下项目中常用到的JavaScript/JQuery代码二(大量实例)

    记录下项目中常用到的JavaScript/JQuery代码一(大量实例) 1.input输入框监听变化 <input type="text" style="widt ...

  3. python 评论楼

    评论楼 从数据库中取出本篇博客的所有评论使用python语句将评论整理成具有层级关系的列表 typename=request.POST.get('typename') comment_list = m ...

  4. python——模块与包2

    模块与包2 1 什么是包 包是一种通过使用.'模块名'来组织python模块名称空间的方式. 无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,都 ...

  5. shell:bash环境

    1.什么是shell shell一般代表两个层面的意思,一个是命令解释器,比如BASH,另外一个是shell脚本. 命令解释器shell的发展史,sh-csh-ksh-tcsh-bash. 2.命令的 ...

  6. JVM 性能调优监控工具

    声明:本文转自<https://www.cnblogs.com/anxiao/p/6796644.html?utm_source=itdadao&utm_medium=referral& ...

  7. Thymeleaf3语法详解和实战

    Thymeleaf3语法详解 Thymeleaf是Spring boot推荐使用的模版引擎,除此之外常见的还有Freemarker和Jsp.Jsp应该是我们最早接触的模版引擎.而Freemarker工 ...

  8. Vue项目模板--和--webpack自动化构建工具的---项目打包压缩使用

    [首先安装node.js]: 1. 从node.js官网下载并安装node,安装过程很简单. 2. npm 版本需要大于 3.0,如果低于此版本需要升级它: # 查看版本 npm -v2.3.0 #升 ...

  9. webstorm 2018.1 激活码 2018.4.8日更新

    安装完成后,打开 WebStorm, 在打开的 License Activation 窗口中选择 License server. 在输入框输入网址即可: http://hb5.s.osidea.cc: ...

  10. 初探Javascript之Canvas

    什么是Canvas <canvas>是 HTML5 新增的元素,可使用JavaScript脚本来绘制图形. canvas是一个矩形区域,您可以控制其每一像素. 引入Canvas ```ht ...