最近接触到登录这块的内容,记录一下~
spring-security手把手教学
前期准备
先配置相应的maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.7.10</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.3.0</version>
</dependency>
编写异常处理类
- AuthenticationEntryPoint 用来解决匿名用户访问无权限资源时的异常
- AccessDeineHandler 用来解决认证过的用户访问无权限资源时的异常
AuthenticationEntryPoint
@Slf4j
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
log.warn("请求未认证 {} {}", request.getRequestURI(), e.getMessage());
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(ResultCode.HTTP_UNAUTHORIZED.getHttpStatusCode());
try (PrintWriter writer = response.getWriter()) {
writer.write(JsonUtil.toJsonString(ApiResultBean.error(ResultCode.HTTP_UNAUTHORIZED)));
writer.flush();
} catch (Exception ex) {
log.error("write response error {}", ex.getMessage());
}
}
}
AccessDeineHandler
@Slf4j
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
log.warn("请求没有操作权限 {} {}", request.getRequestURI(), accessDeniedException.getMessage());
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(ResultCode.HTTP_FORBIDDEN.getHttpStatusCode());
try (PrintWriter writer = response.getWriter()) {
writer.write(JsonUtil.toJsonString(ApiResultBean.error(ResultCode.HTTP_FORBIDDEN)));
writer.flush();
} catch (Exception ex) {
log.error("write response error {}", ex.getMessage());
}
}
}
编写过滤处理器
根据token查询用户信息服务类
@Service
public class JwtUserDetailsService implements UserDetailsService {
@Autowired
private CacheService cacheService;
@Override
public UserDetails loadUserByUsername(String token) throws UsernameNotFoundException {
String userId = JwtUtil.parseToken(token, JwtConfig.SECRET);
// 从redis中获取,在登录接口事先放进redis中的
String cache = cacheService.get(buildLoginKey(userId, token));
if (StringUtils.hasLength(cache)) {
JwtCacheUser jwtCacheUser = JsonUtil.fromJson(cache, JwtCacheUser.class);
if (jwtCacheUser != null) {
return new AuthUser(jwtCacheUser);
}
}
return null;
}
private String buildLoginKey(String userId, String token) {
return CachePrefixEnum.LOGIN_JWT.getPrefixCacheKey() + userId + "_" +
Hashing.md5().hashString(token, Charsets.UTF_8);
}
}
token校验过滤器
@Slf4j
@Component
public class CustomerJwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private JwtUserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = getToken(request);
if (token != null) {
fillAuthUser(token, request);
}
filterChain.doFilter(request, response);
}
String getToken(HttpServletRequest request) {
String token = request.getHeader(JwtConfig.HEADER);
if (null != token && token.startsWith(JwtConfig.PREFIX)) {
token = token.substring(JwtConfig.PREFIX.length());
// JwtUtil校验是否是有效token
String userId = JwtUtil.parseToken(token, JwtConfig.SECRET);
if (null != userId) {
return token;
}
}
return null;
}
void fillAuthUser(String token, HttpServletRequest request) {
UserDetails userDetails = userDetailsService.loadUserByUsername(token);
if (userDetails != null) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, userDetails.getPassword(), userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
log.warn("jwt token not find user {}", token);
}
}
}
其中的JwtConfig和JwtUtil如下:
public class JwtConfig {
public static final String PREFIX = "Bearer";
public static final String HEADER = "Authorization";
public static final String SECRET = "xxxxx";
}
@Slf4j
public class JwtUtil {
public static String generateToken(String id, String secret) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
String sign = JWT.create()
.withClaim("id", id)
.withSubject(id)
.withIssuedAt(Instant.now())
.sign(algorithm);
return sign;
} catch (Exception e) {
log.error("jwt generate error {} {}", id, e.getMessage());
return null;
}
}
public static String parseToken(String token, String secret) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
DecodedJWT jwt = JWT.require(algorithm).build().verify(token);
String id = jwt.getSubject();
return id;
} catch (Exception e) {
log.error("jwt token parse error {} {}", token, e.getMessage());
return null;
}
}
}
编写配置信息
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = false)
public class WebSecurityConfig {
private CustomerJwtAuthenticationTokenFilter authenticationFilter;
@Autowired
public WebSecurityConfig(CustomerJwtAuthenticationTokenFilter authenticationFilter) {
this.authenticationFilter = authenticationFilter;
}
/**
* 不需要进行认证的地址白名单列表
*/
private static final String[] AUTH_WHITELIST = {
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/v1/auth/**",
"/v3/api-docs/**",
"/swagger-ui/**"
// -- swagger ui
};
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable().cors()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint())
.accessDeniedHandler(jwtAccessDeniedHandler())
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers(AUTH_WHITELIST).permitAll()
.anyRequest().authenticated();
//.antMatchers("/**").permitAll();
http.addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint() {
return new JwtAuthenticationEntryPoint();
}
@Bean
public JwtAccessDeniedHandler jwtAccessDeniedHandler() {
return new JwtAccessDeniedHandler();
}
}
由此便完成啦~所有不在白名单中的请求路径均会被认证鉴权,所以登录接口需要被放行~
后面再写登录接口~