使用 SpringSecurity 时,不同接口的鉴权较为麻烦,本文实现了 @Anonymous 和 @HasRole 注解,使得鉴权更加简单。
编写注解 1 2 3 4 5 6 7 8 9 import java.lang.annotation.Target;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Anonymous { }
1 2 3 4 5 6 7 8 9 10 import java.lang.annotation.Target;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface HasRole { Role value () ; }
编写 AnonymousAuthenticationToken 和工具类 1 2 3 4 5 6 7 8 9 10 import java.util.Collection;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;public class AnonymousAuthenticationToken extends PreAuthenticatedAuthenticationToken { public AnonymousAuthenticationToken (Object aPrincipal, Object aCredentials, Collection<? extends GrantedAuthority> anAuthorities) { super (aPrincipal, aCredentials, anAuthorities); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import org.springframework.security.core.Authentication;import org.springframework.security.core.context.SecurityContextHolder;public class SecurityUtils { public static Authentication getAuthentication () { return SecurityContextHolder.getContext().getAuthentication(); } private static Object getPrincipal () { Authentication authentication = getAuthentication(); return authentication == null ? null : authentication.getPrincipal(); } public static Account getCurrentAccount () { if (isAuthenticated()) { return (Account) getPrincipal(); } else { throw new RuntimeException ("Current principal is not an account" ); } } public static void setAuthentication (Authentication authentication) { SecurityContextHolder.getContext().setAuthentication(authentication); } }
编写 Filter 导入请自行补全。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 import java.io.IOException;import java.util.function.Function;import jakarta.servlet.FilterChain;import jakarta.servlet.ServletException;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import lombok.RequiredArgsConstructor;import org.springframework.lang.NonNull;import org.springframework.stereotype.Component;import org.springframework.security.core.authority.AuthorityUtils;import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;import org.springframework.web.filter.OncePerRequestFilter;import org.springframework.web.method.HandlerMethod;import **.Anonymous;import **.SecurityUtils;import **.AnonymousAuthenticationToken;@Component @RequiredArgsConstructor public class AnonymousFilter extends OncePerRequestFilter { private final Function<HttpServletRequest, HandlerMethod> handlerMethodGetter; @Override protected void doFilterInternal ( @NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain ) throws ServletException, IOException { if (SecurityUtils.getAuthentication() == null ) { HandlerMethod handlerMethod = handlerMethodGetter.apply(request); if (handlerMethod != null ) { Anonymous anonymous = handlerMethod.getMethodAnnotation(Anonymous.class); if (anonymous != null ) { AnonymousAuthenticationToken authToken = new AnonymousAuthenticationToken ( "anonymousUser" , "" , AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS" ) ); authToken.setDetails(new WebAuthenticationDetailsSource ().buildDetails(request)); SecurityUtils.setAuthentication(authToken); } } } filterChain.doFilter(request, response); } }
Account 为用户类,需要自行编写 hasRole 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import java.io.IOException;import java.util.function.Function;import jakarta.servlet.FilterChain;import jakarta.servlet.ServletException;import jakarta.servlet.http.HttpServletRequest;import jakarta.servlet.http.HttpServletResponse;import lombok.RequiredArgsConstructor;import org.springframework.lang.NonNull;import org.springframework.stereotype.Component;import org.springframework.security.access.AccessDeniedException;import org.springframework.web.filter.OncePerRequestFilter;import org.springframework.web.method.HandlerMethod;import **.HasRole;import **.Account;import **.SecurityUtils;@Component @RequiredArgsConstructor public class HasRoleFilter extends OncePerRequestFilter { private final Function<HttpServletRequest, HandlerMethod> handlerMethodGetter; @Override protected void doFilterInternal ( @NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain ) throws ServletException, IOException { if (SecurityUtils.isAuthenticated()) { HandlerMethod handlerMethod = handlerMethodGetter.apply(request); if (handlerMethod != null ) { Account account = SecurityUtils.getCurrentAccount(); HasRole hasRole = handlerMethod.getMethodAnnotation(HasRole.class); if (hasRole != null && !account.hasRole(hasRole.value())) { throw new AccessDeniedException ("Does not match the required role" ); } } } filterChain.doFilter(request, response); } }
添加到 SecurityConfig 只写了 filter 的部分,其他部分请视情况自行合并。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import java.util.List;import java.util.Arrays;import lombok.RequiredArgsConstructor;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;import org.springframework.security.web.SecurityFilterChain;import org.springframework.security.web.access.ExceptionTranslationFilter;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import **.filters.*;@Configuration @EnableWebSecurity @EnableMethodSecurity @RequiredArgsConstructor public class SecurityConfig { private final AnonymousFilter anonymousFilter; private final SecurityJwtFilter securityJwtFilter; private final HasRoleFilter hasRoleFilter; @Bean public SecurityFilterChain securityFilterChain (HttpSecurity http) throws Exception { http .addFilterBefore(anonymousFilter, UsernamePasswordAuthenticationFilter.class) .addFilterBefore(securityJwtFilter, UsernamePasswordAuthenticationFilter.class) .addFilterAfter(hasRoleFilter, ExceptionTranslationFilter.class); return http.build(); } }