api regan springboot_springboot+jjwt+security完美解决restful接口无状态鉴权

齐成双
2023-12-01

微服务大行其道的现在,如果我们还在用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可以很直观的看到本项目用了哪些东西,我这个项目使用了很多的包,这里贴出了核心的几个,其他大部分的都会自动引用。

org.springframework.boot

spring-boot-starter-security

org.springframework.security

spring-security-web

provided

org.springframework.security

spring-security-config

provided

io.jsonwebtoken

jjwt

二、登录过滤器

我们采用倒推法,用到什么找什么,这也比较符合XP编程的思想,不写多(无)余(用)的代码,既然要做认证,很明显需要一个过滤器来处理所有需要拦截的请求。

UsernamePasswordAuthenticationFilter是security自己提供的过滤器,我们重写其中的成功方法(successfulAuthentication)来处理我们自己的逻辑,当然根据自己的情况,比如登录失败处理,重写(unsuccessfulAuthentication)即可。

1.成功回调中用到一个TokenAuthenticationHandler,即token认证处理类,该类的主要方法就是借用jwt的机制来生成token,以供后面登录授权使用。

2.往response头信息中放入参数为“Authorization”,值为“Bearer ”+token的值

package com.mos.eboot.tools.jwt;

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

import org.springframework.security.authentication.AuthenticationManager;

import org.springframework.security.core.Authentication;

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

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

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

import javax.servlet.FilterChain;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter{

static final String TOKEN_PREFIX = "Bearer";

static final String HEADER_STRING = "Authorization";

private AuthenticationSuccessHandler successHandler;

public JWTLoginFilter() {

}

public JWTLoginFilter(AuthenticationManager authManager) {

setAuthenticationManager(authManager);

}

@Override

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

TokenAuthenticationHandler tokenAuthenticationHandler = new TokenAuthenticationHandler();

Object obj = auth.getPrincipal();

if(obj != null) {

UserDetails userDetails = (UserDetails)obj;

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

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

}

if(successHandler != null) {

successHandler.onAuthenticationSuccess(req, res, auth);

}

}

public void setSuccessHandler(AuthenticationSuccessHandler successHandler) {

this.successHandler = successHandler;

}

}

JWTAuthenticationToken

package com.mos.eboot.tools.jwt;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

import static java.util.Collections.emptyList;

public class JWTAuthenticationToken extends UsernamePasswordAuthenticationToken{

private static final long serialVersionUID = 1L;

public JWTAuthenticationToken(Object principal) {

super(principal,null,emptyList());

}

@Override

public Object getCredentials() {

return super.getCredentials();

}

@Override

public Object getPrincipal() {

return super.getPrincipal();

}

}

TokenAuthenticationHandler

package com.mos.eboot.tools.jwt;

import io.jsonwebtoken.Claims;

import io.jsonwebtoken.Jwts;

import io.jsonwebtoken.SignatureAlgorithm;

import java.io.Serializable;

import java.util.Date;

import java.util.HashMap;

import java.util.Map;

/**

* @author 小尘哥

*/

public class TokenAuthenticationHandler implements Serializable {

private static final long serialVersionUID = 1L;

private static final String CLAIM_KEY_CREATED = "created";

private static final String CLAIM_KEY_SUBJECT = "subject";

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

private static final Long DEFAULT_EXPIRATION = 864000L;

private String secret = DEFAULT_SECRET;

private Long EXPIRATION = DEFAULT_EXPIRATION;

public TokenAuthenticationHandler() {

}

public String getSubjectFromToken(String token) {

String subject;

try {

final Claims claims = getClaimsFromToken(token);

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

} catch (Exception e) {

subject = null;

}

return subject;

}

private Claims getClaimsFromToken(String token) {

Claims claims;

try {

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

} catch (Exception e) {

claims = null;

}

return claims;

}

private Date generateExpirationDate() {

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

}

public String generateToken(String subject) {

Map claims = new HashMap();

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

claims.put(CLAIM_KEY_SUBJECT, subject);

return generateToken(claims);

}

String generateToken( Map claims) {

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

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

}

}

三、认证

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

package com.mos.eboot.tools.jwt;

import org.apache.commons.lang3.StringUtils;

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

import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

import java.io.IOException;

public class JWTAuthenticationFilter extends GenericFilterBean {

static final String HEADER_STRING = "Authorization";

static final String TOKEN_PREFIX = "Bearer";

@Override

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

HttpServletRequest req = (HttpServletRequest)request;

String token = req.getHeader(HEADER_STRING);

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

TokenAuthenticationHandler tokenAuthenticationHandler = new TokenAuthenticationHandler();

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

if(StringUtils.isNotBlank(subject)) {

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

}

}

filterChain.doFilter(request,response);

}

}

四、调用

@Override

protected void configure(HttpSecurity http) throws Exception {

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

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

.addFilterBefore(loginFilter(), UsernamePasswordAuthenticationFilter.class)

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

}

//注入登录校验类

@Bean

public JWTLoginFilter loginFilter() throws Exception {

JWTLoginFilter loginFilter = new JWTLoginFilter(authenticationManager());

loginFilter.setSuccessHandler(loginAuthenticationSuccessHandler);

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

response.setContentType("application/json");

response.getWriter().write(FastJsonUtils

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

});

return loginFilter;

}

@Bean

public DaoAuthenticationProvider authenticationProvider() {

DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();

authenticationProvider.setUserDetailsService(userDetailsService());

authenticationProvider.setPasswordEncoder(passwordEncoder());

authenticationProvider.setHideUserNotFoundExceptions(false);

return authenticationProvider;

}

@Bean

@Override

public UserDetailsService userDetailsService() {

return new UserService();

}

//重写密码加密方法

@Bean

public Md5PasswordEncoder passwordEncoder() {

Md5PasswordEncoder passwordEncoder = new Md5PasswordEncoder();

passwordEncoder.setIterations(1);

return passwordEncoder;

}

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

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

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

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

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

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

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

import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**

* @author 小尘哥

*/

@Service("userService")

public class UserService implements IUserService {

@Resource

private ISysUserService sysUserService;

@Override

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

return sysUserService.getByUsername(username);

}

}

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

 类似资料: