@Data
@Configuration(proxyBeanMethods = false)
@ConfigurationProperties(prefix = "jwt")
public class SecurityProperties {
/** Request Headers : Authorization */
private String header;
/** 令牌前缀,最后留个空格 Bearer */
private String tokenStartWith;
/** 必须使用最少88位的Base对该令牌进行编码 */
private String baseSecret;
private String secret;
/** 令牌过期时间 此处单位/毫秒 */
private Long tokenValidityInSeconds;
/** 在线用户 key,根据 key 查询 redis 中在线用户的数据 */
private String onlineKey;
/** 验证码 key */
private String codeKey;
public String getTokenStartWith() {
return tokenStartWith + " ";
}
}
从token中获取用户信息、过期时验证token等的封装
@Slf4j
@Component
public class TokenUtil {
@Autowired
private SecurityProperties properties;
/**
* 权限缓存前缀
*/
private static final String REDIS_PREFIX_AUTH = "auth:";
/**
* 用户信息缓存前缀
*/
private static final String REDIS_PREFIX_USER = "user-details:";
/**
* redis repository
*/
@Autowired
private RedisUtils redisUtils;
/**
* 获取用户名
*
* @param token Token
* @return String
*/
public String getUsernameFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims != null ? claims.getSubject() : null;
}
/**
* 获取过期时间
*
* @param token Token
* @return Date
*/
public Date getExpiredFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims != null ? claims.getExpiration() : null;
}
/**
* 获得 Claims
*
* @param token Token
* @return Claims
*/
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(properties.getSecret())
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
log.warn("getClaimsFromToken exception", e);
claims = null;
}
return claims;
}
/**
* 计算过期时间
*
* @return Date
*/
private Date generateExpired() {
return new Date(System.currentTimeMillis() + properties.getTokenValidityInSeconds() * 1000);
}
/**
* 判断 Token 是否过期
*
* @param token Token
* @return Boolean
*/
private Boolean isTokenExpired(String token) {
Date expirationDate = getExpiredFromToken(token);
return expirationDate.before(new Date());
}
/**
* 生成 Token
*
* @param userDetails 用户信息
* @return String
*/
public String generateToken(UserDetails userDetails) {
String secret=properties.getSecret();
String token = Jwts.builder()
.setSubject(userDetails.getUsername())
.setExpiration(generateExpired())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
String key = REDIS_PREFIX_AUTH + userDetails.getUsername() + ":" + token;
redisUtils.set(key, token, properties.getTokenValidityInSeconds() / 1000);
putUserDetails(userDetails);
return token;
}
/**
* 验证 Token
*
* @param token Token
* @return Boolean
*/
public Boolean validateToken(String token) {
final String username = getUsernameFromToken(token);
String key = REDIS_PREFIX_AUTH + username+ ":" + token;
Object data = redisUtils.get(key);
String redisToken = data == null ? null : data.toString();
return StringUtils.isNotEmpty(token) && !isTokenExpired(token) && token.equals(redisToken);
}
/**
* 移除 Token
*
* @param token Token
*/
public void removeToken(String token) {
final String username = getUsernameFromToken(token);
String key = REDIS_PREFIX_AUTH + username+ ":" + token;
redisUtils.del(key);
delUserDetails(username);
}
/**
* 获得用户信息 Json 字符串
*
* @param token Token
* @return String
*/
protected String getUserDetailsString(String token) {
final String username = getUsernameFromToken(token);
String key = REDIS_PREFIX_USER + username;
Object data = redisUtils.get(key);
return data == null ? null : data.toString();
}
/**
* 获得用户信息
*
* @param token Token
* @return UserDetails
*/
public UserDetails getUserDetails(String token) {
String userDetailsString = getUserDetailsString(token);
if (userDetailsString != null) {
return new Gson().fromJson(userDetailsString, JwtUser.class);
}
return null;
}
/**
* 存储用户信息
*
* @param userDetails 用户信息
*/
private void putUserDetails(UserDetails userDetails) {
String key = REDIS_PREFIX_USER + userDetails.getUsername();
redisUtils.set(key, new Gson().toJson(userDetails), properties.getTokenValidityInSeconds() / 1000);
}
/**
* 删除用户信息
*
* @param username 用户名
*/
private void delUserDetails(String username) {
String key = REDIS_PREFIX_USER + username;
redisUtils.del(key);
}
public String getToken(HttpServletRequest request) {
final String requestHeader = request.getHeader(properties.getHeader());
if (requestHeader != null && requestHeader.startsWith(properties.getTokenStartWith())) {
return requestHeader.substring(7);
}
return null;
}
public static void main(String[] args) {
String key = Base.getEncoder().encodeToString("123".getBytes());
Claims claims = Jwts.parser().setSigningKey(key)
.parseClaimsJws("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFwcCIsIndyaXRlIl0sInVpbiI6MSwiZXhwIjoxNTc1MDE1ODgzLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiYjdiYjQ1NTQtNTQ4OS00YTg5LWI3NjQtNzNjODI0YzljNGMyIiwiY2xpZW50X2lkIjoibHZoYWliYW8ifQ.x7QZxRAR1wuX_YNLi6EzRJ1iaKr1rIEUgjtYF0oSx5k").getBody();
System.out.println(JSON.toJSONString(claims));
}
}
其中生成token的方法将用户信息也存在了redis中,在 validateToken方法中又从Redis中判断响应键是否存在,从而验证token‘
public class TokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final TokenUtil tokenUtil;
public TokenConfigurer(TokenUtil tokenUtil){
this.tokenUtil = tokenUtil;
}
@Override
public void configure(HttpSecurity http) {
TokenFilter customFilter = new TokenFilter(tokenUtil);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}
@Configuration
@EnableWebMvc
public class ConfigurerAdapter implements WebMvcConfigurer {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
// 允许cookies跨域
config.setAllowCredentials(true);
// #允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
config.addAllowedOriginPattern("*");
// #允许访问的头信息,*表示全部
config.addAllowedHeader("*");
// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
config.setMaxAge(18000L);
// 允许提交请求的方法类型,*表示全部允许
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
AuthenticationEntryPoint 用来解决匿名用户访问无权限资源时的异常
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
//拓展AuthenticationEntryPoint接口启动身份验证方案。
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
// 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401 响应
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException == null ? "Unauthorized" : authException.getMessage());
}
}
AccessDeineHandler 用来解决认证过的用户访问无权限资源时的异常
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
//当用户在没有授权的情况下访问受保护的REST资源时,将调用此方法发送403 Forbidden响应
response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
}
}
/**
* 用于标记匿名访问方法
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnonymousAccess {
}
使用:
/**
* 访问首页提示
* @return /
*/
@GetMapping("/")
@AnonymousAccess
public String index() {
return "Backend service started successfully";
}
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private TokenUtil tokenUtil;
@Autowired
private CorsFilter corsFilter;
@Autowired
private JwtAuthenticationEntryPoint authenticationErrorHandler;
@Autowired
private JwtAccessDeniedHandler jwtAccessDeniedHandler;
@Autowired
private ApplicationContext applicationContext;
@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
// 去除 ROLE_ 前缀
return new GrantedAuthorityDefaults("");
}
@Bean
public PasswordEncoder passwordEncoder() {
// 密码加密方式
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 搜寻匿名标记 url: @AnonymousAccess
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();
Set<String> anonymousUrls = new HashSet<>();
for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
HandlerMethod handlerMethod = infoEntry.getValue();
AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
if (null != anonymousAccess) {
//如果这个方法上面有@ AnonymousAccess则在anonymousUrls集合中加入
anonymousUrls.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
}
}
httpSecurity
// 禁用 CSRF
.csrf().disable()
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
// 授权异常
.exceptionHandling()
.authenticationEntryPoint(authenticationErrorHandler)
.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("/v2/api-docs-ext").permitAll()
//.antMatchers("/api/wxmp/**").permitAll()
// 文件
.antMatchers("/avatar/**").permitAll()
.antMatchers("/file/**").permitAll()
// 阿里巴巴 druid
.antMatchers("/druid/**").permitAll()
.antMatchers("/api/canvas/**").permitAll()
// 放行OPTIONS请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 自定义匿名访问所有url放行 : 允许匿名和带权限以及登录用户访问
.antMatchers(anonymousUrls.toArray(new String[0])).permitAll()
// 所有请求都需要认证
.anyRequest().authenticated()
.and().apply(securityConfigurerAdapter());//将SecurityConfigurerAdapter应用于此SecurityBuilder并调用SecurityConfigurerAdapter.setBuilder(SecurityBuilder) 。
}
//apply方法:用于进一步自定义的SecurityConfigurerAdapter
private TokenConfigurer securityConfigurerAdapter() {
return new TokenConfigurer(tokenUtil);
}
}
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- sceh.cn 版权所有 湘ICP备2023017654号-4
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务