分享

详细!SpringBoot整合SpringSecurity实现JWT认证

 昵称10087950 2022-07-28 发布于江苏

小Hub领读:

最近一个安全项目,参考了这个例子,还可以做


作者:智慧zhuhuix

https://blog.csdn.net/jpgzhu/article/details/105200598

前言

微架构,之前调用和应用程序的直接应用程序,即应用程序启动和应用程序,作为互联网应用程序的应用程序,通过其应用程序的API接口(API)返回5个应用程序的API接口(API),应用程序和应用程序。

微架构下,传统的认证方式适用于应用运行机制,而传统的认证方式适用于应用运行机制,而传统的认证方式适用于应用运行机制站点的单点登录(SSO)场景

目录

该文会通过创建 SpringBoot 项目集成 SpringSecurity,实现完整的 JWT 认证机制,主要步骤如下:

  1. 创建 SpringBoot 工程

  2. 导入 SpringSecurity 与 JWT 的相关依赖

  3. 定义 SpringSecurity 需要的基础处理类

  4. 制造JWT token 工具类

  5. 实现令牌 验证的过滤器

  6. SpringSecurity的关键配置

  7. 编写 Controller 进行测试

1、创建SpringBoot工程

图片

2、导入SpringSecurity与JWT的相关依赖

pom 文件加入以下依赖

 		<!--Security框架-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
...
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.10.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.10.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.10.6</version>
</dependency>


3. 定义 SpringSecurity 需要的基础处理类

application.yml 配置中加入 jwt 配置信息:

#jwt
jwt:
header: Authorization
# 令牌前缀
token-start-with: Bearer
# 使用Base64对该令牌进行编码
base64-secret: XXXXXXXXXXXXXXXX(制定您的密钥)
# 令牌过期时间 此处单位/毫秒
token-validity-in-seconds: 14400000

创建一个 jwt 灵活的配置,并注入 Spring,便于类程序中调用

@Data
@Configuration
@ConfigurationProperties(prefix = "jwt")
public class JwtSecurityProperties {

/** Request Headers :Authorization */
private String header;

/** 令牌前缀,最后留个空格 Bearer */
private String tokenStartWith;

/** Base64对该令牌进行编码 */
private String base64Secret;

/** 令牌过期时间 此处单位/毫秒 */
private Long tokenValidityInSeconds;

/**返回令牌前缀 */
public String getTokenStartWith() {
return tokenStartWith + " ";
}
}

定义无权限访问类

@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
}
}

定义认证失败处理类

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException)
throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException==null?"Unauthorized":authException.getMessage());
}
}

4.制造JWT代币工具类

工具类实现创建令牌与校验令牌功能

@Slf4j
@Component
public class JwtTokenUtils implements InitializingBean {

private final JwtSecurityProperties jwtSecurityProperties;
private static final String AUTHORITIES_KEY = "auth";
private Key key;

public JwtTokenUtils(JwtSecurityProperties jwtSecurityProperties) {
this.jwtSecurityProperties = jwtSecurityProperties;
}

@Override
public void afterPropertiesSet() {

byte[] keyBytes = Decoders.BASE64.decode(jwtSecurityProperties.getBase64Secret());
this.key = Keys.hmacShaKeyFor(keyBytes);
}


public String createToken (Map<String, Object> claims) {
return Jwts.builder()
.claim(AUTHORITIES_KEY, claims)
.setId(UUID.randomUUID().toString())
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + jwtSecurityProperties.getTokenValidityInSeconds()))
.compressWith(CompressionCodecs.DEFLATE)
.signWith(key,SignatureAlgorithm.HS512)
.compact();
}

public Date getExpirationDateFromToken(String token) {
Date expiration;
try {
final Claims claims = getClaimsFromToken(token);
expiration = claims.getExpiration();
} catch (Exception e) {
expiration = null;
}
return expiration;
}

public Authentication getAuthentication(String token) {
Claims claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();

Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());

HashMap map =(HashMap) claims.get("auth");

User principal = new User(map.get("user").toString(), map.get("password").toString(), authorities);

return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}

public boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(key).parseClaimsJws(authToken);
return true;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
log.info("Invalid JWT signature.");
e.printStackTrace();
} catch (ExpiredJwtException e) {
log.info("Expired JWT token.");
e.printStackTrace();
} catch (UnsupportedJwtException e) {
log.info("Unsupported JWT token.");
e.printStackTrace();
} catch (IllegalArgumentException e) {
log.info("JWT token compact of handler are invalid.");
e.printStackTrace();
}
return false;
}

private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}
}


5. implementation token 验证的过滤器

该类继承 OncePerRequestFilter,它能够确保在请求中通过一次过滤
该类使用 JwtToken 只顾工具类进行令牌校验

@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

private JwtTokenUtils jwtTokenUtils;

public JwtAuthenticationTokenFilter(JwtTokenUtils jwtTokenUtils) {
this.jwtTokenUtils = jwtTokenUtils;
}

@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
JwtSecurityProperties jwtSecurityProperties = SpringContextHolder.getBean(JwtSecurityProperties.class);
String requestRri = httpServletRequest.getRequestURI();
//获取request token
String token = null;
String bearerToken = httpServletRequest.getHeader(jwtSecurityProperties.getHeader());
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(jwtSecurityProperties.getTokenStartWith())) {
token = bearerToken.substring(jwtSecurityProperties.getTokenStartWith().length());
}

if (StringUtils.hasText(token) && jwtTokenUtils.validateToken(token)) {
Authentication authentication = jwtTokenUtils.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
log.debug("set Authentication to security context for '{}', uri: {}", authentication.getName(), requestRri);
} else {
log.debug("no valid JWT token found, uri: {}", requestRri);
}
filterChain.doFilter(httpServletRequest, httpServletResponse);

}
}

可见SpringBoot官方让重复执行的过滤器实现一次过程执行的解决方案,见官网地址:https://docs./spring-boot/docs/current/reference/htmlsingle/#howto-disable-registration-of -a-servlet-or-filter
在 SpringBoot 启动类中,加入以下代码:

    @Bean
public FilterRegistrationBean registration(JwtAuthenticationTokenFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}

6. SpringSecurity的关键配置

SpringBoot推荐使用配置类来代替xml配置,该类中涉及了以上几个bean来供安全使用

  • JwtAccessDeniedHandler :无权限访问

  • jwtAuthenticationEntryPoint :认证失败处理

  • jwtAuthenticationTokenFilter :令牌验证的过滤器

package com.zhuhuix.startup.security.config;

import com.fasterxml.jackson.core.filter.TokenFilter;
import com.zhuhuix.startup.security.security.JwtAccessDeniedHandler;
import com.zhuhuix.startup.security.security.JwtAuthenticationEntryPoint;
import com.zhuhuix.startup.security.security.JwtAuthenticationTokenFilter;
import com.zhuhuix.startup.security.utils.JwtTokenUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
* Spring Security配置类
*
* @author zhuhuix
* @date 2020-03-25
*/


@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtTokenUtils jwtTokenUtils;

public WebSecurityConfig(JwtAccessDeniedHandler jwtAccessDeniedHandler, JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint, JwtTokenUtils jwtTokenUtils) {

this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.jwtTokenUtils = jwtTokenUtils;

}

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {

httpSecurity
// 禁用 CSRF
.csrf().disable()

// 授权异常
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)

// 防止iframe 造成跨域
.and()
.headers()
.frameOptions()
.disable()

// 不创建会话
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)

.and()
.authorizeRequests()

// 放行静态资源
.antMatchers(
HttpMethod.GET,
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/webSocket/**"
).permitAll()

// 放行swagger
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/*/api-docs").permitAll()

// 放行文件访问
.antMatchers("/file/**").permitAll()

// 放行druid
.antMatchers("/druid/**").permitAll()

// 放行OPTIONS请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()

//允许匿名及登录用户访问
.antMatchers("/api/auth/**", "/error/**").permitAll()
// 所有请求都需要认证
.anyRequest().authenticated();

// 禁用缓存
httpSecurity.headers().cacheControl();

// 添加JWT filter
httpSecurity
.apply(new TokenConfigurer(jwtTokenUtils));

}

public class TokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

private final JwtTokenUtils jwtTokenUtils;

public TokenConfigurer(JwtTokenUtils jwtTokenUtils){

this.jwtTokenUtils = jwtTokenUtils;
}

@Override
public void configure(HttpSecurity http) {
JwtAuthenticationTokenFilter customFilter = new JwtAuthenticationTokenFilter(jwtTokenUtils);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}

}


7.编写Controller进行测试

登录逻辑:提交用户和密码参数,返回token

@Slf4j
@RestController
@RequestMapping("/api/auth")
@Api(tags = "系统授权接口")
public class AuthController {

private final JwtTokenUtils jwtTokenUtils;

public AuthController(JwtTokenUtils jwtTokenUtils) {
this.jwtTokenUtils = jwtTokenUtils;
}

@ApiOperation("登录授权")
@GetMapping(value = "/login")
public String login(String user,String password){
Map map = new HashMap();
map.put("user",user);
map.put("password",password);
return jwtTokenUtils.createToken(map);
}
}

使用 IDEA Rest Client 测试如下:图片图片
验证逻辑:传递token,验证成功后返回用户信息
图片图片
token 验证错误返回401:
图片


(完)

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多