项目使用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的验证方案的更多相关文章

  1. Spring Security框架下实现两周内自动登录"记住我"功能

    本文是Spring Security系列中的一篇.在上一篇文章中,我们通过实现UserDetailsService和UserDetails接口,实现了动态的从数据库加载用户.角色.权限相关信息,从而实 ...

  2. 在Spring Security框架下JWT的实现细节原理

    一.回顾JWT的授权及鉴权流程 在笔者的上一篇文章中,已经为大家介绍了JWT以及其结构及使用方法.其授权与鉴权流程浓缩为以下两句话 授权:使用可信用户信息(用户名密码.短信登录)换取带有签名的JWT令 ...

  3. Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(一)

    标题 Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(一) 技术 Spring Boot 2.Spring Security 5.JWT 运行环境 ...

  4. Spring Security框架进阶、自定义登录

      1.Spring Security框架进阶 1.1 Spring Security简介 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安 ...

  5. Spring Security框架入门

    1.Spring Security框架入门 1.1 Spring Security简介 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框 ...

  6. 在Spring Boot框架下使用WebSocket实现聊天功能

    上一篇博客我们介绍了在Spring Boot框架下使用WebSocket实现消息推送,消息推送是一对多,服务器发消息发送给所有的浏览器,这次我们来看看如何使用WebSocket实现消息的一对一发送,模 ...

  7. Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(二)

    Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(二) 摘要 上一篇https://javaymw.com/post/59我们已经实现了基本的登录和t ...

  8. Spring Security框架中踢人下线技术探索

    1.背景 在某次项目的开发中,使用到了Spring Security权限框架进行后端权限开发的权限校验,底层集成Spring Session组件,非常方便的集成Redis进行分布式Session的会话 ...

  9. 在Spring Boot框架下使用WebSocket实现消息推送

    Spring Boot的学习持续进行中.前面两篇博客我们介绍了如何使用Spring Boot容器搭建Web项目(使用Spring Boot开发Web项目)以及怎样为我们的Project添加HTTPS的 ...

随机推荐

  1. uiautomatorviewer工具的安装与使用

    Android自动化测试应用<一><uiautomatorviewer工具的安装与使用> OldKe 关注 2018.01.25 18:00* 字数 488 阅读 2083评论 ...

  2. 前端页面的适配使用rem换算

    前端页面的适配使用rem换算 https://www.cnblogs.com/liangxuru/p/6970629.html 注:本文转载之处:https://www.cnblogs.com/ann ...

  3. django自定义模板标签

    # 创建自定义模板标签目录 django_project_name app_name templatetags (创建Python Packge,注意一定要用templatetags这个名字) my_ ...

  4. Spring、SpringMVC区别

    1. 为什么使用Spring ? 1). 方便解耦,简化开发 通过Spring提供的IoC容器,可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合. 2). AOP编程的 ...

  5. [BZOJ1596]电话网络

    Problem 给你一棵树,可以在每个点上选择造塔或不造,每座塔可以覆盖这个节点和相邻节点,问覆盖整棵树的最小塔数. Solution 看到这道题的第一眼,我就觉得是一题贪心题,但看见出题的时候分类在 ...

  6. Saiku数据库迁移后的刷新脚本-Shell脚本读取数据库中的数据(二十三)

    Saiku数据库迁移后的刷新脚本 之前有谈过对saiku中的数据进行刷新,因为saiku默认会从缓存中查询数据,但是配置不使用缓存又会效率低下... 所以这里就需要做一个数据刷新,每次ETL之后都需要 ...

  7. fastreport窗口重置(适用于属性、数据等窗口显示不出来)

    找到如下路径: C:/Users/账户名/AppData/Local/FastReport/FastReport.config 删除即可. 记得先退出使用FastReport的程序,再删除

  8. Python 习题一

    1.使用while循环输入 1 2 3 4 5 6 8 9 10 # Author:Tony.lou i = 1 while i < 11: if i == 7: pass else: prin ...

  9. effective java——32用EnumSet代替位域

    什么是位域?为什么用到它?先来看一个例子: public class Test { public static final byte STYLE_BOLD = 1<<0; // 1 pub ...

  10. 下载MNIST数据集脚本input_data源码

    # Copyright 2015 Google Inc. All Rights Reserved.## Licensed under the Apache License, Version 2.0 ( ...