一 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
二 新建配置类
@Configuration
@EnableMethodSecurity
@EnableWebSecurity
public class SecurityCfg {
@Value("${server.cors}")
private Boolean isCors;
@Autowired
private JwtFilter jwtFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf(httpSecurityCsrfConfigurer -> httpSecurityCsrfConfigurer.disable())
.formLogin(httpSecurityFormLoginConfigurer -> httpSecurityFormLoginConfigurer.disable())
.httpBasic(httpSecurityHttpBasicConfigurer -> httpSecurityHttpBasicConfigurer.disable())
.logout(httpSecurityLogoutConfigurer -> httpSecurityLogoutConfigurer.disable());
if (isCors!=null && isCors){
httpSecurity.cors(configurer -> configurer.configurationSource(request -> {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedHeader("*");
return corsConfiguration;
}));
}
httpSecurity.addFilterAt(jwtFilter, UsernamePasswordAuthenticationFilter.class);
httpSecurity.addFilterBefore(new ExceptionFilter(),JwtFilter.class);
//httpSecurity.addFilterAt(anonymousFilter, AnonymousAuthenticationFilter.class);
httpSecurity.sessionManagement(httpSecuritySessionManagementConfigurer ->
httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
httpSecurity.authorizeHttpRequests(auth-> auth
.requestMatchers("/swagger-ui/**").permitAll()
.requestMatchers("/css/**","/js/**","/favicon.ico",
"/robots.txt","/manifest.json","/icon.png","/avatar.png","/sw.js"
).permitAll() //静态资源
//访问都需要VIEW权限
.anyRequest().hasAuthority("VIEW")
);
httpSecurity.exceptionHandling(configurer -> {
configurer.authenticationEntryPoint((request, response, accessDeniedException) -> {
throw new BadCredentialsException("未认证");
});
configurer.accessDeniedHandler((request, response, accessDeniedException) -> {
throw new AuthenticationException("未授权");
});
});
return httpSecurity.build();
}
}
三 实现 UserDetailsService 接口
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration
spring boot的 org.springframework.boot.autoconfigure.AutoConfiguration.imports 里引入该自动配置类.
当没有 ConditionalOnMissingBean 指定的这几个类时,会自动配置一个 InMemoryUserDetailsManager.所以只要实现一个 UserDetailsService 类就可以了.
@AutoConfiguration
@ConditionalOnClass(AuthenticationManager.class)
@Conditional(MissingAlternativeOrUserPropertiesConfigured.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class,
AuthenticationManagerResolver.class }, type = "org.springframework.security.oauth2.jwt.JwtDecoder")
@ConditionalOnWebApplication(type = Type.SERVLET)
public class UserDetailsServiceAutoConfiguration {
@Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
ObjectProvider<PasswordEncoder> passwordEncoder) {
SecurityProperties.User user = properties.getUser();
List<String> roles = user.getRoles();
return new InMemoryUserDetailsManager(User.withUsername(user.getName())
.password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
.roles(StringUtils.toStringArray(roles))
.build());
}
}
@Service
public class UserService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return null;
}
}
四 使用jwt
1. 引入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.19.2</version>
</dependency>
2.
/**
* 从请求头获取token并装载用户
*/
@Component
public class JwtFilter extends OncePerRequestFilter {
@Autowired
private UserService userService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取访问ip
String fuck = request.getHeader("X-Forwarded-For");
System.out.println("请求ip:"+fuck);
Enumeration<String> headers = request.getHeaderNames();
if (headers!=null){
String tmp = "";
while (headers.hasMoreElements()){
tmp = tmp+" "+headers.nextElement();
}
System.out.println("gan:"+tmp);
}
//从header获取token
String token = request.getHeader("Authentication");
//header中没有token,则从cookie获取token
if (token==null || token.isEmpty()){
Cookie[] cookies = request.getCookies();
if (cookies==null || cookies.length==0){
filterChain.doFilter(request,response);
return;
}
for (Cookie cookie:cookies){
if (cookie.getName().equals("token")){
token = cookie.getValue();
break;
}
}
//cookie中也没有token,则交给下一个过滤器
if (token==null || token.equals("")){
filterChain.doFilter(request,response);
return;
}
}
DecodedJWT decodedJWT = null;
try {
decodedJWT = JWT.decode(token);
}catch (Exception e){
return;
}
List<String> audience = decodedJWT.getAudience();
String username = audience.getFirst();
//用户密码作为加密盐
if (username == null){
throw new BadCredentialsException("认证信息缺失");
}
UserInfo userInfo = userService.getUserByUsername(username);
if (userInfo == null){
throw new BadCredentialsException("用户不存在");
}
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(userInfo.getPassword())).build();
try {
jwtVerifier.verify(token);
}catch (SignatureVerificationException e){
e.printStackTrace();
response.sendError(401);
return;
}catch (TokenExpiredException e){
response.setStatus(401);
return;
}
Set<GrantedAuthority> authorities = new HashSet<>();
for (UserRole role:userInfo.getRoles()){
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(role.getRole());
authorities.add(simpleGrantedAuthority);
}
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
username,
userInfo.getPassword(),
authorities
);
authenticationToken.setDetails(userInfo);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request,response);
}
}
异常处理
spring boot @RestControllerAdvice 定义的全局异常捕获,只能捕获 servlet(Controller) 后的异常.
所以,在Controller上定义的hasAuthority,如果未登陆或者没有权限,可以交给全局异常处理程序处理.
Spring Security配置文件里,JwtFilter中的异常,最好直接抛出,新建一个ExceptionFilter来处理.
public class ExceptionFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
filterChain.doFilter(request,response);
}catch (Exception e){
if (e instanceof DisabledException){
//已被封禁 请到 /block 查看封禁原因,如继续有疑问请联系客服.
}
else if (e instanceof BadCredentialsException){
//未登录 需要登录后再继续
response.setContentType("application/json;charset=UTF-8");
response.setStatus(401);
response.getWriter().write("");
}
else if (e instanceof AuthenticationException){
//没有权限 没有权限进行该操作
response.setContentType("application/json;charset=UTF-8");
response.setStatus(403);
PrintWriter printWriter = response.getWriter();
printWriter.write("");
printWriter.flush();
}
else {
e.printStackTrace();
}
}
}
}
@RestControllerAdvice
public class ExceptionHandle {
//无权限
@ExceptionHandler(AccessDeniedException.class)
public void denied(HttpServletRequest request, HttpServletResponse response){
ResMap<String> resMap = ResMap.fail("没有权限进行该操作");
resMap.setCode(403);
response.setStatus(403);
response.setContentType("application/json;charset=UTF-8");
try {
String tem = new ObjectMapper().writeValueAsString(resMap);
response.getWriter().write(tem);
}catch (Exception ignore){}
}
//未登录
@ExceptionHandler(AuthenticationException.class)
public void authenticationException(HttpServletResponse response){
//未登录
ResMap<String> resMap = ResMap.fail("需要登录后再继续");
resMap.setCode(401);
response.setStatus(401);
response.setContentType("application/json;charset=UTF-8");
try {
String tem = new ObjectMapper().writeValueAsString(resMap);
response.getWriter().write(tem);
}catch (Exception ignore){}
}
//无Square权限
@ExceptionHandler(SquareDisableException.class)
public void squareDisabled(Exception e,HttpServletResponse response){
ResMap<String> resMap = ResMap.fail("square功能未开启");
resMap.setCode(315);
response.setStatus(315);
response.setContentType("application/json;charset=UTF-8");
try {
String tem = new ObjectMapper().writeValueAsString(resMap);
response.getWriter().write(tem);
}catch (Exception ignore){}
}
@ExceptionHandler(Exception.class)
public void fuck(Exception e,HttpServletResponse response){
e.printStackTrace();
// System.out.println("fuck2 ");
response.setStatus(400);
}
}