共计 7948 个字符,预计需要花费 20 分钟才能阅读完成。
大家好,我是不才陈某~
认证、受权是实战我的项目中必不可少的局部,而 Spring Security 则将作为首选平安组件,因而陈某新开了《Spring Security 进阶》这个专栏,写一写从单体架构到 OAuth2 分布式架构的认证受权。
Spring security 这里就不再过多介绍了,置信大家都用过,也都恐怖过,相比 Shiro 而言,Spring Security 更加重量级,之前的 SSM 我的项目更多企业都是用的 Shiro,然而 Spring Boot 进去之后,整合 Spring Security 更加不便了,用的企业也就多了。
明天陈某就来介绍一下在前后端拆散的我的项目中如何应用 Spring Security 进行登录认证。文章的目录如下:
前后端拆散认证的思路
前后端拆散不同于传统的 web 服务,无奈应用 session,因而咱们采纳 JWT 这种无状态机制来生成 token,大抵的思路如下:
-
客户端调用服务端登录接口,输出用户名、明码登录,登录胜利返回两个token,如下:
- accessToken:客户端携带这个 token 拜访服务端的资源
- refreshToken:刷新令牌,一旦 accessToken 过期了,客户端须要应用 refreshToken 从新获取一个 accessToken。因而 refreshToken 的过期工夫个别大于 accessToken。
- 客户申请头中携带 accessToken 拜访服务端的资源,服务端对 accessToken 进行鉴定(验签、是否生效 ….),如果这个 accessToken 没有问题则放行。
- accessToken一旦过期须要客户端携带 refreshToken 调用刷新令牌的接口从新获取一个新的accessToken。
我的项目搭建
陈某应用的是 Spring Boot 框架,演示我的项目新建了两个模块,别离是common-base
、security-authentication-jwt
。
1、common-base 模块
这是一个形象进去的公共模块,这个模块次要放一些专用的类,目录如下:
2、security-authentication-jwt 模块
一些须要定制的类,比方 security 的全局配置类、Jwt 登录过滤器的配置类,目录如下:
3、五张表
权限设计依据业务的需要往往有不同的设计,陈某用的 RBAC 标准,次要波及到五张表,别离是 用户表 、 角色表 、 权限表 、 用户 <-> 角色表 、 角色 <-> 权限表,如下图:
上述几张表的 SQL 会放在案例源码中(这几张表字段为了省事,设计的并不全,本人依据业务逐渐拓展即可)
登录认证过滤器
登录接口的逻辑写法有很多种,明天陈某介绍一种应用过滤器的定义的登录接口。
Spring Security 默认的表单登录认证的过滤器是UsernamePasswordAuthenticationFilter
,这个过滤器并不适用于前后端拆散的架构,因而咱们须要自定义一个过滤器。
逻辑很简略,参照 UsernamePasswordAuthenticationFilter
这个过滤器革新一下,代码如下:
认证胜利处理器 AuthenticationSuccessHandler
上述的过滤器接口一旦认证胜利,则会调用 AuthenticationSuccessHandler 进行解决,因而咱们能够自定义一个认证胜利处理器进行本人的业务解决,代码如下:
陈某仅仅返回了accessToken、refreshToken,其余的业务逻辑解决本人欠缺。
认证失败处理器 AuthenticationFailureHandler
同样的,一旦登录失败,比方用户名或者明码谬误等等,则会调用 AuthenticationFailureHandler 进行解决,因而咱们须要自定义一个认证失败的处理器,其中依据异样信息返回特定的 JSON 数据给客户端,代码如下:
逻辑很简略,AuthenticationException有不同的实现类,依据异样的类型返回特定的提示信息即可。
AuthenticationEntryPoint 配置
AuthenticationEntryPoint这个接口当 用户未通过认证拜访受爱护的资源 时,将会调用其中的 commence()
办法进行解决,比方客户端携带的 token 被篡改,因而咱们须要自定义一个 AuthenticationEntryPoint 返回特定的提示信息,代码如下:
AccessDeniedHandler 配置
AccessDeniedHandler这处理器当认证胜利的用户拜访受爱护的资源,然而 权限不够,则会进入这个处理器进行解决,咱们能够实现这个处理器返回特定的提示信息给客户端,代码如下:
UserDetailsService 配置
UserDetailsService这个类是用来加载用户信息,包含 用户名 、 明码 、 权限 、 角色 汇合 …. 其中有一个办法如下:
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
在认证逻辑中 Spring Security 会调用这个办法依据客户端传入的 username 加载该用户的详细信息,这个办法须要实现的逻辑如下:
- 明码匹配
- 加载权限、角色汇合
咱们须要实现这个接口,从 数据库 加载用户信息,代码如下:
其中的 LoginService 是依据用户名从数据库中查问出明码、角色、权限,代码如下:
UserDetails这个也是个接口,其中定义了几种办法,都是围绕着 用户名 、 明码 、 权限 + 角色汇合 这三个属性,因而咱们能够实现这个类拓展这些字段,SecurityUser代码如下:
拓展 :UserDetailsService 这个类的实现个别波及到 5 张表,别离是 用户表 、 角色表 、 权限表 、 用户 <-> 角色对应关系表 、 角色 <-> 权限对应关系表 ,企业中的实现必须遵循RBAC 设计规定。这个规定陈某前面会具体介绍。
Token 校验过滤器
客户端申请头携带了 token,服务端必定是须要针对每次申请解析、校验 token,因而必须定义一个 Token 过滤器,这个过滤器的次要逻辑如下:
- 从申请头中获取accessToken
- 对 accessToken 解析、验签、校验过期工夫
- 校验胜利,将 authentication 存入 ThreadLocal 中,这样不便后续间接获取用户详细信息。
下面只是最根底的一些逻辑,理论开发中还有特定的解决,比方将用户的详细信息放入 Request 属性中、Redis 缓存中,这样可能实现 feign 的令牌中继成果。
校验过滤器的代码如下:
刷新令牌接口
accessToken一旦过期,客户端必须携带着 refreshToken 从新获取令牌,传统 web 服务是放在 cookie 中,只须要服务端实现刷新,齐全做到无感知令牌续期,然而前后端拆散架构中必须由客户端拿着 refreshToken 调接口手动刷新。
代码如下:
次要逻辑很简略,如下:
- 校验refreshToken
- 从新生成 accessToken、refreshToken 返回给客户端。
留神:理论生产中 refreshToken 令牌的生成形式、加密算法能够和 accessToken 不同。
登录认证过滤器接口配置
上述定义了一个认证过滤器JwtAuthenticationLoginFilter,这个是用来登录的过滤器,然而并没有注入退出 Spring Security 的过滤器链中,须要定义配置,代码如下:
/**
* @author 公号:码猿技术专栏
* 登录过滤器的配置类
*/
@Configuration
public class JwtAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
/**
* userDetailService
*/
@Qualifier("jwtTokenUserDetailsService")
@Autowired
private UserDetailsService userDetailsService;
/**
* 登录胜利处理器
*/
@Autowired
private LoginAuthenticationSuccessHandler loginAuthenticationSuccessHandler;
/**
* 登录失败处理器
*/
@Autowired
private LoginAuthenticationFailureHandler loginAuthenticationFailureHandler;
/**
* 加密
*/
@Autowired
private PasswordEncoder passwordEncoder;
/**
* 将登录接口的过滤器配置到过滤器链中
* 1. 配置登录胜利、失败处理器
* 2. 配置自定义的 userDetailService(从数据库中获取用户数据)* 3. 将自定义的过滤器配置到 spring security 的过滤器链中,配置在 UsernamePasswordAuthenticationFilter 之前
* @param http
*/
@Override
public void configure(HttpSecurity http) {JwtAuthenticationLoginFilter filter = new JwtAuthenticationLoginFilter();
filter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
// 认证胜利处理器
filter.setAuthenticationSuccessHandler(loginAuthenticationSuccessHandler);
// 认证失败处理器
filter.setAuthenticationFailureHandler(loginAuthenticationFailureHandler);
// 间接应用 DaoAuthenticationProvider
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
// 设置 userDetailService
provider.setUserDetailsService(userDetailsService);
// 设置加密算法
provider.setPasswordEncoder(passwordEncoder);
http.authenticationProvider(provider);
// 将这个过滤器增加到 UsernamePasswordAuthenticationFilter 之前执行
http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
}
}
所有的逻辑都在 public void configure(HttpSecurity http)
这个办法中,如下:
- 设置认证胜利处理器loginAuthenticationSuccessHandler
- 设置认证失败处理器loginAuthenticationFailureHandler
- 设置 userDetailService 的实现类JwtTokenUserDetailsService
- 设置加密算法 passwordEncoder
- 将 JwtAuthenticationLoginFilter 这个过滤器退出到过滤器链中,间接退出到 UsernamePasswordAuthenticationFilter 这个过滤器之前。
Spring Security 全局配置
上述仅仅配置了登录过滤器,还须要在全局配置类做一些配置,如下:
- 利用登录过滤器的配置
- 将登录接口、令牌刷新接口放行,不须要拦挡
- 配置AuthenticationEntryPoint、AccessDeniedHandler
- 禁用 session,前后端拆散 +JWT 形式不须要 session
- 将 token 校验过滤器 TokenAuthenticationFilter 增加到过滤器链中,放在 UsernamePasswordAuthenticationFilter 之前。
残缺配置如下:
/**
* @author 公众号:码猿技术专栏
* @EnableGlobalMethodSecurity 开启权限校验的注解
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationSecurityConfig jwtAuthenticationSecurityConfig;
@Autowired
private EntryPointUnauthorizedHandler entryPointUnauthorizedHandler;
@Autowired
private RequestAccessDeniedHandler requestAccessDeniedHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {http.formLogin()
// 禁用表单登录,前后端拆散用不上
.disable()
// 利用登录过滤器的配置,配置拆散
.apply(jwtAuthenticationSecurityConfig)
.and()
// 设置 URL 的受权
.authorizeRequests()
// 这里须要将登录页面放行,permitAll()示意不再拦挡,/login 登录的 url,/refreshToken 刷新 token 的 url
//TODO 此处失常我的项目中放行的 url 还有很多,比方 swagger 相干的 url,druid 的后盾 url,一些动态资源
.antMatchers("/login","/refreshToken")
.permitAll()
//hasRole()示意须要指定的角色能力拜访资源
.antMatchers("/hello").hasRole("ADMIN")
// anyRequest() 所有申请 authenticated() 必须被认证
.anyRequest()
.authenticated()
// 解决异常情况:认证失败和权限有余
.and()
.exceptionHandling()
// 认证未通过,不容许拜访异样处理器
.authenticationEntryPoint(entryPointUnauthorizedHandler)
// 认证通过,然而没权限处理器
.accessDeniedHandler(requestAccessDeniedHandler)
.and()
// 禁用 session,JWT 校验不须要 session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// 将 TOKEN 校验过滤器配置到过滤器链中,否则不失效,放到 UsernamePasswordAuthenticationFilter 之前
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class)
// 敞开 csrf
.csrf().disable();
}
// 自定义的 Jwt Token 校验过滤器
@Bean
public TokenAuthenticationFilter authenticationTokenFilterBean() {return new TokenAuthenticationFilter();
}
/**
* 加密算法
* @return
*/
@Bean
public PasswordEncoder getPasswordEncoder(){return new BCryptPasswordEncoder();
}
}
正文的很具体了,有不了解的认真看一下。
案例源码曾经上传 GitHub,关注公众号:码猿技术专栏,回复关键词:9529 获取!
测试
1、首先测试登录接口,postman 拜访 http://localhost:2001/securit…,如下:
能够看到,胜利返回了两个 token。
2、申请头不携带 token,间接申请 http://localhost:2001/securit…,如下:
能够看到,间接进入了 EntryPointUnauthorizedHandler 这个处理器。
3、携带 token 拜访 http://localhost:2001/securit…,如下:
胜利拜访,token 是无效的。
4、刷新令牌接口测试,携带一个过期的令牌拜访如下:
5、刷新令牌接口测试,携带未过期的令牌测试,如下:
能够看到,胜利返回了两个新的令牌。
源码追踪
以上一系列的配置齐全是参照 UsernamePasswordAuthenticationFilter 这个过滤器,这个是 web 服务表单登录的形式。
Spring Security 的原理就是一系列的过滤器组成,登录流程也是一样,起初在 org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#doFilter()
办法,进行认证匹配,如下:
attemptAuthentication()
这个办法次要作用就是获取客户端传递的 username、password,封装成 UsernamePasswordAuthenticationToken
交给 ProviderManager
的进行认证,源码如下:
ProviderManager 次要流程是调用抽象类 AbstractUserDetailsAuthenticationProvider#authenticate()
办法,如下图:
retrieveUser()
办法就是调用 userDetailService 查问用户信息。而后认证,一旦认证胜利或者失败,则会调用对应的失败、胜利处理器进行解决。
总结
Spring Security 尽管比拟重,然而真的好用,尤其是实现 Oauth2.0 标准,非常简单不便。
案例源码曾经上传 GitHub,关注公众号:码猿技术专栏,回复关键词:9529 获取!
最初说一句(别白嫖,求关注)
陈某每一篇文章都是精心输入,曾经写了 3 个专栏,整顿成PDF,获取形式如下:
- 《Spring Cloud 进阶》PDF:关注公号:【码猿技术专栏】回复关键词 Spring Cloud 进阶 获取!
- 《Spring Boot 进阶》PDF:关注公号:【码猿技术专栏】回复关键词 Spring Boot 进阶 获取!
- 《Mybatis 进阶》PDF:关注公号:【码猿技术专栏】回复关键词 Mybatis 进阶 获取!
如果这篇文章对你有所帮忙,或者有所启发的话,帮忙 点赞 、 在看 、 转发 、 珍藏,你的反对就是我坚持下去的最大能源!
关注公号:【码猿技术专栏】,公众号内有超赞的粉丝福利,回复:加群,能够退出技术探讨群,和大家一起探讨技术,吹牛逼!