使用SpringSecurity搭建授权认证服务(1) -- 基本demo认证原理

使用SpringSecurity搭建授权认证服务(1) -- 基本demo

登录认证是做后台开发的最基本的能力,初学就知道一个interceptor或者filter拦截所有请求,然后判断参数是否合理,如此即可。当涉及到某些接口权限的时候,则if-else判断以下,也是没问题的。
但如果判断多了,业务逻辑也掺杂在一起,降低可读性的同时也不同意扩展和维护。于是就出现了apache shiro, spring security这样的框架,抽离出认证授权判断。
由于我现在的项目都是给予springboot的,那选择spring security就方便很多。接下来基于此构建我的认证授权服务: 基于Token的认证授权服务。

项目初始化

第一个版本,项目初始化https://github.com/Ryan-Miao/spring-security-token-login-server/releases/tag/v1.0

首先学习两个单词:

authentication 身份验证

authorized 经授权的

我们登录鉴权就是两个步骤,先认证登录,然后权限校验。对应到Spring Security里就是 AuthenticationManager和AccessDecisionManager,前者负责对用户凭证进行认证,后者对认证后的权限进行校验。

首先,创建一个基本的springboot项目。

使用SpringSecurity搭建授权认证服务(1) -- 基本demo认证原理

引入Springboot, Mybatis, Redis, Swagger, Spring Security

配置全局异常拦截ExceptionInterceptor

配置Redis缓存,这里使用redisson,也可以直接使用starter

配置Spring Security Config

Spring Security参照官方文档配置即可。接下来是自定义和可以修改的地方。

数据表权限模型

本项目简单使用 user - role -permission的模型。

使用SpringSecurity搭建授权认证服务(1) -- 基本demo认证原理

一个user可以有多个role

一个role可以指定给多个user

一个role可以拥有多个permission

一个permission也可以从属于多个role

权限判定通过判断user是否拥有permission来决定。通过role实现了user和permission之间的解耦,创建多个role模型,绑定对应的权限,当添加新用户的时候,直接指定role就可以授权。

Spring Security自带了org.springframework.security.provisioning.JdbcUserDetailsManager,它里面的模型为user-group-authority. 即用户归属用户组,用户组有权限。差不多可以和当前模型一一对应。

认证流程

大体认证流程和涉及的核心类如下:

使用SpringSecurity搭建授权认证服务(1) -- 基本demo认证原理

ApplicationFilterChain的filter顺序:

使用SpringSecurity搭建授权认证服务(1) -- 基本demo认证原理

FilterChainProxy(springSecurityFilterChain)执行认证的顺序, 忽略的url将不命中任何filter, 而需要认证的url将通过VirtualFilterChain来认证。

使用SpringSecurity搭建授权认证服务(1) -- 基本demo认证原理

使用Token认证

starter默认启用的基于用户名密码的basic认证。

使用SpringSecurity搭建授权认证服务(1) -- 基本demo认证原理

通过UsernamePasswordAuthenticationFilter组装UsernamePasswordAuthenticationToken去认证。

通过org.springframework.security.web.authentication.解析header Authorization, 然后组装成UsernamePasswordAuthenticationToken去给AuthenticationManager认证。

我们要做的就是模仿UsernamePasswordAuthenticationFilter或者BasicAuthenticationFilter解析header将我们的认证凭证传递给AuthenticationManager.

两种方式我实现了一遍,最终选择了基于UsernamePasswordAuthenticationFilter来实现。

/** * @author Ryan Miao * @date 2019/5/30 10:11 * @see org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter */ public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public TokenAuthenticationFilter(String defaultFilterProcessesUrl) { super(defaultFilterProcessesUrl); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { boolean debug = this.logger.isDebugEnabled(); String token = TokenUtils.readTokenFromRequest(request); if (StringUtils.isBlank(token)) { throw new UsernameNotFoundException("token not found"); } if (debug) { this.logger.debug("Token Authentication Authorization header found "); } //token包装类, 使用principal来装载token UsernamePasswordAuthenticationToken tokenAuthenticationToken = new UsernamePasswordAuthenticationToken( token, null); //AuthenticationManager 负责解析 Authentication authResult = getAuthenticationManager() .authenticate(tokenAuthenticationToken); if (debug) { this.logger.debug("Authentication success: " + authResult); } return authResult; } /** * 重写认证成功后的方法,不跳转. */ @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { if (this.logger.isDebugEnabled()) { this.logger.debug( "Authentication success. Updating SecurityContextHolder to contain: " + authResult); } SecurityContextHolder.getContext().setAuthentication(authResult); getRememberMeServices().loginSuccess(request, response, authResult); if (this.eventPublisher != null) { this.eventPublisher.publishEvent( new InteractiveAuthenticationSuccessEvent(authResult, this.getClass())); } chain.doFilter(request, response); } }

TokenUtils来从request里拿到我们的凭证,我这里是从cookie里取出token的值。

封装给UsernamePasswordAuthenticationToken的username字段

交给getAuthenticationManager()去认证

认证Provider

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpggjj.html