Spring Security 自定义 @Anonymous 和 @HasRole 注解

使用 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 {
/**
* Get authentication.
*
* @return authentication.
*/
public static Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}

/**
* Get principal.
*
* @return principal.
*/
private static Object getPrincipal() {
Authentication authentication = getAuthentication();
return authentication == null ? null : authentication.getPrincipal();
}

/**
* Get current account.
*
* @return current account.
*/
public static Account getCurrentAccount() {
if (isAuthenticated()) {
return (Account) getPrincipal();
} else {
throw new RuntimeException("Current principal is not an account");
}
}

/**
* Set authentication.
*
* @param authentication authentication.
*/
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) {
// get the handler method
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);
}
}
}

// continue to other filters
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()) {
// get the handler method
HandlerMethod handlerMethod = handlerMethodGetter.apply(request);

if (handlerMethod != null) {
Account account = SecurityUtils.getCurrentAccount();
HasRole hasRole = handlerMethod.getMethodAnnotation(HasRole.class);

// the ordinal of the required role must be greater than or equal to the ordinal of the current role
if (hasRole != null && !account.hasRole(hasRole.value())) {
throw new AccessDeniedException("Does not match the required role");
}
}
}

// continue to other filters
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();
}
}

Spring Security 自定义 @Anonymous 和 @HasRole 注解
https://blog.zhanganzhi.com/zh-CN/2023/08/f2d4df6ce0b9/
作者
Andy Zhang
发布于
2023年8月6日
许可协议