项目使用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. Ubuntu配置MYSQL远程连接

    一.修改mysql权限 1.mysql -u root -p  回车输入密码 2.use mysql:      打开数据库 3.将host设置为%表示任何ip都能连接mysql,当然您也可以将hos ...

  2. linux常用命令 命令管道符

    多命令顺序执行 多命令顺序执行 多命令执行符 格式 作用 : 命令1:命令2 多个命令顺序执行,命令之间没有任何逻辑联系 && 命令1&&命令2 逻辑与 当命令1正确执 ...

  3. devexpress—WPF

    https://docs.devexpress.com/WPF/7875/index Ocelot 资源汇总 https://www.cnblogs.com/shanyou/p/10363360.ht ...

  4. C# 应用程序单例(禁止多开) 获取.net版本号 以及 管理员权限

    Mutex不仅提供跨线程的服务,还提供跨进程的服务.当在构造函数中为Mutex指定名称时,则会创建一个命名了的Mutex.其他线程创建Mutex时,如果指定的名称相同,则返回同一个互斥体,不论该线程位 ...

  5. Linux内核分析--进程创建,执行,切换

    学号:351 原创作品转载请注明出处本实验来源 https://github.com/mengning/linuxkernel/ 实验要求 从整理上理解进程创建.可执行文件的加载和进程执行进程切换,重 ...

  6. java-js知识库之一——canvas绘制9*9乘法表

    不知不觉一年又要过去了,软件这一行入坑快两年了,一直不知道这两年干了些啥,也不知道自己到底会些什么,工作也是些简单的东西,谁都能做,对未来也是很茫然.今天和同事优化数据库,头都是懵的,很多东西都感觉似 ...

  7. @Autowired注解与@Qualifier注解搭配使用

    问题:当一个接口实现由两个实现类时,只使用@Autowired注解,会报错,如下图所示 实现类1 实现类2 controller中注入 然后启动服务报错,如下所示: Exception encount ...

  8. JAVA中接口与抛出异常的相关知识

    1.接口概念:接口可以理解为一种特殊的类,由全局常量和公共的抽象方法所组成. 类是一种具体实现体,而接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部数据,也不关心这些类里方法的实现细节,它只 ...

  9. linux 免密码登陆

    1.Linux下生成密钥 ssh-keygen的命令手册,通过”man ssh-keygen“命令: 通过命令”ssh-keygen -t rsa“ 生成之后会在用户的根目录生成一个 “.ssh”的文 ...

  10. 173zrx个人简介

    码云链接:https://gitee.com/zhrx-617/codes/947dbs2fi5kw3jz8hc0ma74 效果图: 源代码: <html> <head> &l ...