redis jwt spring boot spring security 实现api token 验证
Posted haha174
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis jwt spring boot spring security 实现api token 验证相关的知识,希望对你有一定的参考价值。
文章地址:http://www.haha174.top/article/details/258083
项目源码:https://github.com/haha174/jwt-token.git
具体的实际效果可以看考这里 目前已经部署一个 个人测试机器上面:
http://cloud.codeguoj.cn/api-cloud-server/swagger-ui.html#!/token45controller/loginUsingPOST
相信很多人都调用过api, 一般的大致基本步骤都是先用登陆获得一个token,然后使用token调用api 或者直接给你一个token 凭借token调用api.
那么这里实现一个spring security+ redis+ jwt 发token 和验证token的一个项目。
首先需要安装redis 如果不会的小伙伴可以参考http://www.haha174.top/article/details/257478 这篇博客。
创建一个maven项目导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wen.security</groupId>
<artifactId>jwt-token</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>1.5.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.44</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
</dependencies>
</project>
编写 token 拦截器 通过重写springsecurity AbstractAuthenticationProcessingFilter
public class AuthTokenFilter extends AbstractAuthenticationProcessingFilter
{
Logger logger = LoggerFactory.getLogger(this.getClass());
@Value("${token.header}")
private String tokenHeader;
@Value("${token.name}")
private String tokenName;
public AuthTokenFilter(RequestMatcher matcher)
{
super(matcher);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException
{
String authToken = request.getHeader(this.tokenHeader);
if(StringUtils.isEmpty(authToken))
{
authToken = request.getParameter(this.tokenName);
}
logger.debug("start to check token:{} *************");
if (authToken == null)
{
throw new AuthenticationCredentialsNotFoundException("Access Token is not provided");
}
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(authToken, null);
return getAuthenticationManager().authenticate(authentication);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication auth) throws IOException, ServletException
{
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(auth);
SecurityContextHolder.setContext(context);
chain.doFilter(request, response);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException
{
SecurityContextHolder.clearContext();
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
String message;
if (authException.getCause() != null)
{
message = authException.getCause().getMessage();
}
else
{
message = authException.getMessage();
}
BaseResponse baseResponse = new BaseResponse();
baseResponse.setStatusCode(IConstants.RESPONSE_STATUS_CODE_FAILED);
baseResponse.setStatusMsg(message);
byte[] body = new ObjectMapper().writeValueAsBytes(baseResponse);
response.getOutputStream().write(body);
}
}
自定义校验
@Component
public class AuthTokenProvider implements AuthenticationProvider
{
Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
protected HttpServletRequest request;
@Autowired
TokenService tokenService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException
{
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if(auth!=null && auth.isAuthenticated())
{
return new UsernamePasswordAuthenticationToken(auth.getPrincipal(), null, new ArrayList<>());
}
String token = (String) authentication.getPrincipal();
if (token != null)
{
if (!tokenService.checkToken(token))
{
throw new CredentialsExpiredException("Access Token is expired. Please login again.");
}
}
else
{
throw new BadCredentialsException("Invalid token String.");
}
logger.debug("Authenticated successfully.");
return new UsernamePasswordAuthenticationToken(token, null, new ArrayList<>());
}
@Override
public boolean supports(Class<?> authentication)
{
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
配置spring security 将上述的拦截器配置到spring security 的拦截器链上 和拦截规则
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebAuthConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Bean
public AuthTokenFilter authenticationTokenFilterBean() throws Exception {
List<String> pathsToSkip = Arrays.asList("/login");
List<String> processingPath = Arrays.asList("/service/**","/logout");
SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, processingPath);
AuthTokenFilter filter = new AuthTokenFilter(matcher);
filter.setAuthenticationManager(authenticationManager);
return filter;
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers("/", "/*.html", "/**/favicon.ico", "/**/*.html", "/**/*.css", "/**/*.js").permitAll()
.and().addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
httpSecurity.cors().configurationSource(corsConfigurationSource());
httpSecurity.logout().disable();
httpSecurity.headers().cacheControl();
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
filter.setForceEncoding(true);
return filter;
}
}
生成token 使用json web toekn
public class JavaWebToken {
private static Logger log = LoggerFactory.getLogger(JavaWebToken.class);
//该方法使用HS256算法和Secret:bankgl生成signKey
private static Key getKeyInstance() {
//We will sign our JavaWebToken with our ApiKey secret
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary("token");
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
return signingKey;
}
//使用HS256签名算法和生成的signingKey最终的Token,claims中是有效载荷
public static String createJavaWebToken(Map<String, Object> claims) {
return Jwts.builder()
.setIssuer("Jersey-Security-Basic")//设置发行人
.setSubject("subject")//设置抽象主题
.setAudience("login")//设置角色
.setExpiration(getDate())//过期时间
.setIssuedAt(new Date())//设置现在时间
.setClaims(claims)
.signWith( SignatureAlgorithm.HS256,getKeyInstance())
.compact();
}
//解析Token,同时也能验证Token,当验证失败返回null
public static Map<String, Object> parserJavaWebToken(String jwt) {
try {
Map<String, Object> jwtClaims =
Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(jwt).getBody();
return jwtClaims;
} catch (Exception e) {
log.error("json web token verify failed");
return null;
}
}
public static Date getDate(){
try {
/* SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date=format.parse(new Date().getTime()+new TokenProperties().getExpires()*1000+"");
return date;*/
long currentTime = System.currentTimeMillis() ;
currentTime +=30*60*1000*2;
Date date=new Date(currentTime);
return date;
}catch (Exception e){
e.printStackTrace();
long currentTime = System.currentTimeMillis() ;
currentTime +=30*60*1000*2;
Date date=new Date(currentTime);
return date;
}
}
}
配置swagger
在请求头中加入header
@Bean
public Docket demoApi() {
ParameterBuilder aParameterBuilder = new ParameterBuilder();
aParameterBuilder.name("Authorization").description("input the token for authentication either in the authorization field or in the token field").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
ParameterBuilder aParameterBuilder1 = new ParameterBuilder();
aParameterBuilder1.name("token").description("input the token for authentication either in the authorization field or in the token field").modelRef(new ModelRef("string")).parameterType("query").required(false).build();
List<Parameter> aParameters = new ArrayList<Parameter>();
aParameters.add(aParameterBuilder.build());
aParameters.add(aParameterBuilder1.build());
return new Docket(DocumentationType.SWAGGER_2).apiInfo(getApiInfo()).useDefaultResponseMessages(false).globalOperationParameters(aParameters)
.select() .apis(RequestHandlerSelectors.basePackage("com.wen.token.web")).build();
}
protected ApiInfo getApiInfo()
{
return new ApiInfo("Rest Web Service", "total api Rest Web Service " + new Date(), "", "",
new Contact("cxhc", "", ""), "", "",new ArrayList<VendorExtension>());
}
整合redis 可以参考这篇博客
http://www.haha174.top/article/details/251216
只需要启动应用 访问http://localhost:8080/swagger-ui.html#/
----
以上是关于redis jwt spring boot spring security 实现api token 验证的主要内容,如果未能解决你的问题,请参考以下文章
spring boot + redis 实现session共享分析
Spring boot->redis value值出现两个双引号