Spring Security框架下Restful Token的验证方案
项目使用Restful的规范,权限内容的访问,考虑使用Token验证的权限解决方案。
验证方案(简要概括):
首先,用户需要登陆,成功登陆后返回一个Token串;
然后用户访问有权限的内容时需要上传Token串进行权限验证
代码方案:
Spring MVC + Spring Security + Redis的框架下实现权限验证,此文重点谈谈Spring Security下的Token验证实现。
首先,看看spring security的配置:
<http pattern="/service/secure/**"
entry-point-ref="serviceUnauthorizedEntryPoint"
create-session="stateless">
<!-- Added after moving to Spring Boot 1.3 + Spring Security 4.x, otherwise we could not login with basic auth because of: Expected CSRF token not found TODO: Please, mind, that I did not migrate this XML to Spring Security 4.x except for this element -->
<csrf disabled="true"/> <intercept-url pattern="/service/secure/admin/login*" access="permitAll"/> <custom-filter ref="preTokenAuthenticationFilter" before="PRE_AUTH_FILTER" />
</http>
接下来详细说明配置以及访问流程:
1. 考虑到支持Restful规范,所以spring security需要设置create-session为stateless状态
2. 当访问权限验证失败是,根据Restful规范返回401 Unauthorized,因此需要设定entry-point-ref,重新指向一个自定义的entrypoint如下:
public class ServiceUnauthorizedEntryPoint implements AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(ServiceTokenAuthenticationFilter.class);
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException arg2) throws IOException, ServletException {
// return 401 UNAUTHORIZED status code if the user is not authenticated
logger.debug(" *** UnauthorizedEntryPoint.commence: " + request.getRequestURI());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
3. Token的验证重点在于PRE_AUTH_FILTER的拦截器
关于FllterChain请看官方解释security-filter-chain
另外关于PRE_AUTH_FILTER拦截器的官方解释preauth,我们在此就采取已经被可靠的验证系统验证过的流程即验证Token的合法性,我们看一下这个拦截器的bean设置
<b:bean id="preTokenAuthenticationFilter"
class="com.will.security.token.PreRequestHeaderAuthenticationFilter">
<b:property name="authenticationManager" ref="preAuthenticationManager" />
<b:property name="authenticationFailureHandler" ref="authFailureHandler"/>
<b:property name="principalRequestHeader" value="X-Auth-Token"/>
<b:property name="continueFilterChainOnUnsuccessfulAuthentication" value="false" />
</b:bean> <!-- PreAuthentication manager. -->
<b:bean id="authFailureHandler" class="org.springframework.security.web.authentication.ForwardAuthenticationFailureHandler" >
<b:constructor-arg value="/service/secure/admin/login/failed" />
</b:bean> <b:bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<b:property name="preAuthenticatedUserDetailsService">
<b:bean id="userDetailsServiceWrapper" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<b:property name="userDetailsService" ref="tokenUserDetailsService"/>
</b:bean>
</b:property>
<b:property name="userDetailsChecker">
<b:bean id="tokenUserDetailsChecker" class="com.will.security.token.TokenUserDetailsChecker" />
</b:property>
</b:bean>
<authentication-manager id="preAuthenticationManager">
<authentication-provider ref="preauthAuthProvider" />
</authentication-manager>
PreRequestHeaderAuthenticationFilter里,截取访问的request,然后获取上传的Token串,这里的Token串储存在“SM_UER”的header里,
代码如下:
public class PreRequestHeaderAuthenticationFilter extends
AbstractCustomPreAuthenticatedProcessingFilter { private String principalRequestHeader = "SM_USER";
private String credentialsRequestHeader;
private boolean exceptionIfHeaderMissing = true; @Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
/*获取principal信息*/
String principal = request.getHeader(principalRequestHeader); if (principal == null && exceptionIfHeaderMissing) {
// 对于request进行BadException处理
request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, new BadCredentialsException("No pre-authenticated credentials found in request.")); return "N/A";
} return principal;
} @Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
if (credentialsRequestHeader != null) {
return request.getHeader(credentialsRequestHeader);
} return "N/A";
} public void setPrincipalRequestHeader(String principalRequestHeader) {
Assert.hasText(principalRequestHeader,
"principalRequestHeader must not be empty or null");
this.principalRequestHeader = principalRequestHeader;
} public void setCredentialsRequestHeader(String credentialsRequestHeader) {
Assert.hasText(credentialsRequestHeader,
"credentialsRequestHeader must not be empty or null");
this.credentialsRequestHeader = credentialsRequestHeader;
} /**
* Defines whether an exception should be raised if the principal header is missing.
* Defaults to {@code true}.
*
* @param exceptionIfHeaderMissing set to {@code false} to override the default
* behaviour and allow the request to proceed if no header is found.
*/
public void setExceptionIfHeaderMissing(boolean exceptionIfHeaderMissing) {
this.exceptionIfHeaderMissing = exceptionIfHeaderMissing;
}
}
如果需要详细了解认证流程建议查看PreAuthenticatedAuthenticationProvider的源码,对于provider的配置也就一目了然了
TokenUserDetailsService的代码如下
public class TokenUserDetailsService implements UserDetailsService {
private TokenManager tokenManager;
@Override
public UserDetails loadUserByUsername(String token)
throws UsernameNotFoundException {
if (token.equalsIgnoreCase("N/A")) {
return null;
}
return tokenManager != null ? tokenManager.getUserDetails(token) : null;
}
public void setTokenManager(TokenManager tm) {
this.tokenManager = tm;
}
}
TokenManager负责Token的生成,验证以及删除等等操作
public interface TokenManager {
/**
* Creates a new token for the user and returns its {@link TokenInfo}.
* It may add it to the token list or replace the previous one for the user. Never returns {@code null}.
*/
TokenInfo createNewToken(UserDetails userDetails);
/** Removes all tokens for user. */
//void removeUserDetails(UserDetails userDetails);
/** Removes a single token. */
UserDetails removeToken(String token);
/** Returns user details for a token. */
UserDetails getUserDetails(String token);
/** Returns user details for a username. */
UserDetails getUserDetailsByUsername(String username);
/** Returns a collection with token information for a particular user. */
Collection<TokenInfo> getUserTokens(UserDetails userDetails);
Boolean validateToken(String token);
}
我们可以根据自己的需要比如借助Redis做缓存,或者使用JWT等等,具体可实现自己的TokenManager
Spring Security框架下Restful Token的验证方案的更多相关文章
- Spring Security框架下实现两周内自动登录"记住我"功能
本文是Spring Security系列中的一篇.在上一篇文章中,我们通过实现UserDetailsService和UserDetails接口,实现了动态的从数据库加载用户.角色.权限相关信息,从而实 ...
- 在Spring Security框架下JWT的实现细节原理
一.回顾JWT的授权及鉴权流程 在笔者的上一篇文章中,已经为大家介绍了JWT以及其结构及使用方法.其授权与鉴权流程浓缩为以下两句话 授权:使用可信用户信息(用户名密码.短信登录)换取带有签名的JWT令 ...
- Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(一)
标题 Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(一) 技术 Spring Boot 2.Spring Security 5.JWT 运行环境 ...
- Spring Security框架进阶、自定义登录
1.Spring Security框架进阶 1.1 Spring Security简介 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安 ...
- Spring Security框架入门
1.Spring Security框架入门 1.1 Spring Security简介 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框 ...
- 在Spring Boot框架下使用WebSocket实现聊天功能
上一篇博客我们介绍了在Spring Boot框架下使用WebSocket实现消息推送,消息推送是一对多,服务器发消息发送给所有的浏览器,这次我们来看看如何使用WebSocket实现消息的一对一发送,模 ...
- Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(二)
Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(二) 摘要 上一篇https://javaymw.com/post/59我们已经实现了基本的登录和t ...
- Spring Security框架中踢人下线技术探索
1.背景 在某次项目的开发中,使用到了Spring Security权限框架进行后端权限开发的权限校验,底层集成Spring Session组件,非常方便的集成Redis进行分布式Session的会话 ...
- 在Spring Boot框架下使用WebSocket实现消息推送
Spring Boot的学习持续进行中.前面两篇博客我们介绍了如何使用Spring Boot容器搭建Web项目(使用Spring Boot开发Web项目)以及怎样为我们的Project添加HTTPS的 ...
随机推荐
- 高可用性的负载均衡方案之lvs+keepalived和haproxy+heartbeat区别
高可用性的负载均衡方案 目前使用比较多的就是标题中提到的这两者,其实lvs和haproxy都是实现的负载均衡的作用,keepalived和heartbeat都是提高高可用性的,避免单点故障.那么他们为 ...
- ASP.NET MVC Display Mode 移动端视图 配置对微信内置浏览器的识别
最近在捣鼓一个稍微有点low的商城网站,没有计划做app却要求有个wap版,而前端又没有做成响应式,时间WTF,直接利用了asp.net mvc的Display Mode Provider. 使用方式 ...
- Ubuntu18.04下给PyCharm创建快捷方式
Ubuntu18.04下给PyCharm创建快捷方式 该方法 WebStorm.PyCharm.Clion 等都适用. 步骤 终端输入: sudo gedit /usr/share/applicati ...
- 【jenkins】jenkins+maven+gitlab+testng,jenkins配置
电脑版本:windows10企业版 jenkins配置: 1.general配置,这里的配置比较简单,基本默认就可以了 2.源码管理 2.1填写git地址,从你的gitlib项目里去找.不会的自行百度 ...
- Spark基本架构
Spark基本架构图如下: Client:客户端进程,负责提交作业. Driver:一个Spark作业有一个spark context,一个Spark Context对应一个Driver进程,作业的 ...
- 一位资深Java架构师的晋级心得
架构师是什么? 是一个既需要掌控整体又需要洞悉局部瓶颈并依据具体的业务场景给出解决方案的团队领导型人物.一个架构师得需要足够的想像力,能把各种目标需求进行不同维度的扩展,为目标客户提供更为全面的需求清 ...
- C#对XML操作类
C#对XML操作类 该类包含了对XML文件的创建,添加,读取,删除,修改等操作 //#define isUnity #if isUnity using UnityEngine; #endif usin ...
- spoj Ae2b
题解: 设最后为x1+t1k+t2n,y1+t3k+t4n 显然t1,t4或t2,t3同余(mod 2) 然后exgcd一下 代码: #include<bits/stdc++.h> #de ...
- CICD自动化发版系统设计简介
第一篇. 版本迭代是每一个互联网公司必须经历的,尤其是中小型公司,相信不少人踩到过很多坑.接下来的一系列文章将介绍我设计的自动化发版系统! 很多公司没有把配置独立出去,代码的构建.发版通过一个Jenk ...
- 修改create-react-app支持多入口
使用Facebook官方脚手架create-react-app创建React应用,默认只能生成一个SPA,入口是index.html.虽然,SPA的页面切换可以使用前台路由框架方便(比如React-R ...