springboot+jjwt+security完美解决restful接口无状态鉴权

Easter79
• 阅读 467

微服务大行其道的现在,如果我们还在用wsdl之类的提供接口,给人的感觉就会很low,虽然说不能为了炫技而炫技,但是既然restful接口已经越来越流行,必然有它的道理。 本文我们不讨论restful接口的好处,旨在解决使用restful时候的权限控制问题。

springboot本身已经提供了很好的spring security的支持,我们只需要实现(或者重写)一部分接口来实现我们的个性化设置即可。本文浅显易懂,没有深入原理(后面文章会将,有需要的小伙伴稍等等~~~)。 思路: 1.通过spring security做授权拦截操作 2.通过jwt根据用户信息生成token以供后面调用 3.将生成的token放到HttpServletResponse头信息中 4.使用的时候从response头中获取token放在request头中提交到后台做认证即可 5.默认超时时间10天

一、pom

惯例还是先上pom,因为pom可以很直观的看到本项目用了哪些东西,我这个项目使用了很多的包,这里贴出了核心的几个,其他大部分的都会自动引用。

  1. <dependency>

  2. <groupId>org.springframework.boot</groupId>

  3. <artifactId>spring-boot-starter-security</artifactId>

  4. </dependency>

  5. <dependency>

  6. <groupId>org.springframework.security</groupId>

  7. <artifactId>spring-security-web</artifactId>

  8. <scope>provided</scope>

  9. </dependency>

  10. <dependency>

  11. <groupId>org.springframework.security</groupId>

  12. <artifactId>spring-security-config</artifactId>

  13. <scope>provided</scope>

  14. </dependency>

  15. <dependency>

  16. <groupId>io.jsonwebtoken</groupId>

  17. <artifactId>jjwt</artifactId>

  18. </dependency>

二、登录过滤器

我们采用倒推法,用到什么找什么,这也比较符合XP编程的思想,不写多(无)余(用)的代码,既然要做认证,很明显需要一个过滤器来处理所有需要拦截的请求。 UsernamePasswordAuthenticationFilter是security自己提供的过滤器,我们重写其中的成功方法(successfulAuthentication)来处理我们自己的逻辑,当然根据自己的情况,比如登录失败处理,重写(unsuccessfulAuthentication)即可。 1.成功回调中用到一个TokenAuthenticationHandler,即token认证处理类,该类的主要方法就是借用jwt的机制来生成token,以供后面登录授权使用。 2.往response头信息中放入参数为“Authorization”,值为“Bearer ”+token的值

  1. package com.mos.eboot.tools.jwt;

  2. import com.mos.eboot.tools.util.FastJsonUtils;

  3. import org.springframework.security.authentication.AuthenticationManager;

  4. import org.springframework.security.core.Authentication;

  5. import org.springframework.security.core.userdetails.UserDetails;

  6. import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

  7. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

  8. import javax.servlet.FilterChain;

  9. import javax.servlet.ServletException;

  10. import javax.servlet.http.HttpServletRequest;

  11. import javax.servlet.http.HttpServletResponse;

  12. import java.io.IOException;

  13. public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter{

  14. static final String TOKEN_PREFIX = "Bearer";

  15. static final String HEADER_STRING = "Authorization";

  16. private AuthenticationSuccessHandler successHandler;

  17. public JWTLoginFilter() {

  18. }

  19. public JWTLoginFilter(AuthenticationManager authManager) {

  20. setAuthenticationManager(authManager);

  21. }

  22. @Override

  23. protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException {

  24. TokenAuthenticationHandler tokenAuthenticationHandler = new TokenAuthenticationHandler();

  25. Object obj = auth.getPrincipal();

  26. if(obj != null) {

  27. UserDetails userDetails = (UserDetails)obj;

  28. String token = tokenAuthenticationHandler.generateToken(FastJsonUtils.toJSONNoConfig(userDetails));

  29. res.addHeader(HEADER_STRING, TOKEN_PREFIX + " " + token);

  30. }

  31. if(successHandler != null) {

  32. successHandler.onAuthenticationSuccess(req, res, auth);

  33. }

  34. }

  35. public void setSuccessHandler(AuthenticationSuccessHandler successHandler) {

  36. this.successHandler = successHandler;

  37. }

  38. }

JWTAuthenticationToken

  1. package com.mos.eboot.tools.jwt;

  2. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

  3. import static java.util.Collections.emptyList;

  4. public class JWTAuthenticationToken extends UsernamePasswordAuthenticationToken{

  5. private static final long serialVersionUID = 1L;

  6. public JWTAuthenticationToken(Object principal) {

  7. super(principal,null,emptyList());

  8. }

  9. @Override

  10. public Object getCredentials() {

  11. return super.getCredentials();

  12. }

  13. @Override

  14. public Object getPrincipal() {

  15. return super.getPrincipal();

  16. }

  17. }

TokenAuthenticationHandler

  1. package com.mos.eboot.tools.jwt;

  2. import io.jsonwebtoken.Claims;

  3. import io.jsonwebtoken.Jwts;

  4. import io.jsonwebtoken.SignatureAlgorithm;

  5. import java.io.Serializable;

  6. import java.util.Date;

  7. import java.util.HashMap;

  8. import java.util.Map;

  9. /**

  10. * @author 小尘哥

  11. */

  12. public class TokenAuthenticationHandler implements Serializable {

  13. private static final long serialVersionUID = 1L;

  14. private static final String CLAIM_KEY_CREATED = "created";

  15. private static final String CLAIM_KEY_SUBJECT = "subject";

  16. private static final String DEFAULT_SECRET = "eboot@secret";

  17. private static final Long DEFAULT_EXPIRATION = 864000L;

  18. private String secret = DEFAULT_SECRET;

  19. private Long EXPIRATION = DEFAULT_EXPIRATION;

  20. public TokenAuthenticationHandler() {

  21. }

  22. public String getSubjectFromToken(String token) {

  23. String subject;

  24. try {

  25. final Claims claims = getClaimsFromToken(token);

  26. subject = claims.get(CLAIM_KEY_SUBJECT).toString();

  27. } catch (Exception e) {

  28. subject = null;

  29. }

  30. return subject;

  31. }

  32. private Claims getClaimsFromToken(String token) {

  33. Claims claims;

  34. try {

  35. claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();

  36. } catch (Exception e) {

  37. claims = null;

  38. }

  39. return claims;

  40. }

  41. private Date generateExpirationDate() {

  42. return new Date(System.currentTimeMillis() + EXPIRATION * 1000);

  43. }

  44. public String generateToken(String subject) {

  45. Map<String, Object> claims = new HashMap<String, Object>();

  46. claims.put(CLAIM_KEY_CREATED, new Date());

  47. claims.put(CLAIM_KEY_SUBJECT, subject);

  48. return generateToken(claims);

  49. }

  50. String generateToken( Map<String, Object> claims) {

  51. return Jwts.builder().setClaims(claims).setExpiration(generateExpirationDate())

  52. .signWith(SignatureAlgorithm.HS512, secret).compact();

  53. }

  54. }

三、认证

从头信息中取出Authorization,然后解析出个人信息,如果个人信息不为空,则将个人信息加密后再放入授权域。

  1. package com.mos.eboot.tools.jwt;

  2. import org.apache.commons.lang3.StringUtils;

  3. import org.springframework.security.core.context.SecurityContextHolder;

  4. import org.springframework.web.filter.GenericFilterBean;

  5. import javax.servlet.FilterChain;

  6. import javax.servlet.ServletException;

  7. import javax.servlet.ServletRequest;

  8. import javax.servlet.ServletResponse;

  9. import javax.servlet.http.HttpServletRequest;

  10. import java.io.IOException;

  11. public class JWTAuthenticationFilter extends GenericFilterBean {

  12. static final String HEADER_STRING = "Authorization";

  13. static final String TOKEN_PREFIX = "Bearer";

  14. @Override

  15. public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {

  16. HttpServletRequest req = (HttpServletRequest)request;

  17. String token = req.getHeader(HEADER_STRING);

  18. if(StringUtils.isNotBlank(token) && token.startsWith(TOKEN_PREFIX)) {

  19. TokenAuthenticationHandler tokenAuthenticationHandler = new TokenAuthenticationHandler();

  20. String subject = tokenAuthenticationHandler.getSubjectFromToken(token.replace(TOKEN_PREFIX, ""));

  21. if(StringUtils.isNotBlank(subject)) {

  22. SecurityContextHolder.getContext().setAuthentication(new JWTAuthenticationToken(subject));

  23. }

  24. }

  25. filterChain.doFilter(request,response);

  26. }

  27. }

四、调用

  1. @Override

  2. protected void configure(HttpSecurity http) throws Exception {

  3. http.csrf().disable().authorizeRequests().antMatchers("/**").authenticated()

  4. .antMatchers(HttpMethod.POST, "/login").permitAll().anyRequest().permitAll().and()

  5. .addFilterBefore(loginFilter(), UsernamePasswordAuthenticationFilter.class)

  6. .addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

  7. }

  8. //注入登录校验类

  9. @Bean

  10. public JWTLoginFilter loginFilter() throws Exception {

  11. JWTLoginFilter loginFilter = new JWTLoginFilter(authenticationManager());

  12. loginFilter.setSuccessHandler(loginAuthenticationSuccessHandler);

  13. loginFilter.setAuthenticationFailureHandler((request, response, exception) -> {

  14. response.setContentType("application/json");

  15. response.getWriter().write(FastJsonUtils

  16. .toJSONString(new ResultModel(ResultStatus.FAIL.getCode(), exception.getMessage())));

  17. });

  18. return loginFilter;

  19. }

  20. @Bean

  21. public DaoAuthenticationProvider authenticationProvider() {

  22. DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();

  23. authenticationProvider.setUserDetailsService(userDetailsService());

  24. authenticationProvider.setPasswordEncoder(passwordEncoder());

  25. authenticationProvider.setHideUserNotFoundExceptions(false);

  26. return authenticationProvider;

  27. }

  28. @Bean

  29. @Override

  30. public UserDetailsService userDetailsService() {

  31. return new UserService();

  32. }

  33. //重写密码加密方法

  34. @Bean

  35. public Md5PasswordEncoder passwordEncoder() {

  36. Md5PasswordEncoder passwordEncoder = new Md5PasswordEncoder();

  37. passwordEncoder.setIterations(1);

  38. return passwordEncoder;

  39. }

实现UserDetailsService 接口,定义自己的获取用户登录方法实现类

  1. package com.mos.eboot.api.config.support;

  2. import com.mos.eboot.api.platform.api.ISysUserService;

  3. import com.mos.eboot.platform.entity.SysUser;

  4. import com.mos.eboot.tools.result.ResultModel;

  5. import org.springframework.security.core.userdetails.UserDetails;

  6. import org.springframework.security.core.userdetails.UsernameNotFoundException;

  7. import org.springframework.stereotype.Service;

  8. import javax.annotation.Resource;

  9. /**

  10. * @author 小尘哥

  11. */

  12. @Service("userService")

  13. public class UserService implements IUserService {

  14. @Resource

  15. private ISysUserService sysUserService;

  16. @Override

  17. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

  18. return sysUserService.getByUsername(username);

  19. }

  20. }


基本以上就可以搞定基础的无状态鉴权了,如果需要更深入的了解,比如权限自定义等,请关注我后面的文章.......

本文分享自微信公众号 - 陌与尘埃(grq100296)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Easter79 Easter79
2年前
SpringBoot自定义序列化的使用方式
场景及需求:项目接入了SpringBoot开发,现在需求是服务端接口返回的字段如果为空,那么自动转为空字符串。例如:\    {        "id":1,        "name":null    },    {        "id":2,        "name":"x
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
2年前
SpringBoot自定义序列化的使用方式
场景及需求:项目接入了SpringBoot开发,现在需求是服务端接口返回的字段如果为空,那么自动转为空字符串。例如:\    {        "id":1,        "name":null    },    {        "id":2,        "name":"x
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k