spring security

https://spring.io/projects/spring-security#overview

https://docs.spring.io/spring-security/reference/index.html

Spring Security integrates with the Servlet Container by using a standard Servlet Filter. This means it works with any application that runs in a Servlet Container.

Spring Security, with first class support for securing both imperative and reactive applications, is a framework that provides authentication, authorization, and protection against common attacks.

Spring Security 的过滤器链的顺序非常重要,因为每个过滤器依赖于前面的过滤器执行特定的功能,才能实现整体的安全控制。

FilterInvocationSecurityMetadataSource

数据库设计

首先要自定义FilterInvocationSecurityMetadataSource 通过FilterInvocationSecurityMetadataSource接口中的getAttributes方法来确定一个请求需要哪些角色,

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

@Component
public class PermissionAuthority implements FilterInvocationSecurityMetadataSource {

@Resource
private RoleMapper roleMapper;

@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {

// 获取请求的URL
String requestUrl = ( (FilterInvocation) object ).getRequestUrl();

// 根据URL获取可以访问角色列表
List<Role> roleList = roleMapper.getListByOperateUrl(requestUrl);

// 判断角色列表是否为空
if( !roleList.isEmpty() && roleList.size() > 0 ){
// 准备数组存放授权列表
String[] roles = new String[roleList.size()];
// 循环角色列表中的每一个元素
for( int i = 0 ; i <= roles.length - 1; i++ ){
// 将当前循环到角色存放到授权列表中
roles[i] = roleList.get(i).getRoleName();
}
// 返回授权列表
return SecurityConfig.createList(roles);
}
// 避免返回null(任何角色都能访问)
return SecurityConfig.createList("PublicPermission");
}

@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}

@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}

AccessDecisionManager

当一个请求走完FilterInvocationSecurityMetadataSource中的getAttributes方法后,接下来就会来到AccessDecisionManager类中进行角色信息的比对,

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

@Component
public class PermissionValid implements AccessDecisionManager {

@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {

// 迭代遍历当前请求的URL授权的所有角色
for( ConfigAttribute attribute : configAttributes ){

// 判断当前账户是否为空
if (authentication == null) {
throw new AccessDeniedException("权限认证失败!");
}

// 获取当前请求的授权角色
String needRole = attribute.getAttribute();

System.out.println( "-------开始认证-------");
// 判断是否是默认权限
if ("PublicPermission".equals(needRole)) {
if (authentication instanceof AnonymousAuthenticationToken) {
throw new BadCredentialsException("未登录");
} else
return;
}
// 获取当前账户所拥有的角色
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
// 迭代遍历当前账户的每一个角色
for (GrantedAuthority authority : authorities) {
// 判断当前账户的角色是否和当前循环到的授权角色相同
if (authority.getAuthority().equals(needRole)) {
return;
}
}
}
// 认证失败
throw new AccessDeniedException("权限认证失败!");
}

@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}

@Override
public boolean supports(Class<?> clazz) {
return true;
}
}

public class SecurityConfig extends WebSecurityConfigurerAdapter {

public class AdminServiceImpl extends ServiceImpl<AdminMapper, Admin> implements AdminService {

http://127.0.0.1:8001/hello 启动项目,访问/hello接口会自动跳转到登录页面,这个登录页面是Spring Security预定义

spring security 过滤器链 过滤器顺序

Spring Security 的过滤器链是一系列过滤器的有序组合,每个过滤器负责处理特定的安全功能。过滤器链的顺序在安全处理过程中非常关键,因为每个过滤器依赖之前的过滤器执行其功能。当配置 Spring Security 的过滤器链时,理解和管理过滤器的顺序对于确保正确的安全行为至关重要。

过滤器链的默认顺序

Spring Security 有默认的过滤器顺序,这些过滤器按照严格的顺序排列,以下是常用的过滤器及其顺序:

  1. ChannelProcessingFilter

    • 确保请求使用正确的通道(HTTP 或 HTTPS)。
  2. WebAsyncManagerIntegrationFilter

    • 用于管理异步请求的安全上下文集成。
  3. SecurityContextPersistenceFilter

    • 管理 SecurityContext 的生命周期。
  4. HeaderWriterFilter

    • 为请求添加安全相关的 HTTP 头。
  5. CsrfFilter

    • 处理 CSRF 令牌。
  6. LogoutFilter

    • 处理注销请求。
  7. UsernamePasswordAuthenticationFilter

    • 处理基于表单的登录请求。
  8. DefaultLoginPageGeneratingFilter

    • 在未定义登录页面时生成默认登录页面。
  9. BasicAuthenticationFilter

    • 处理 HTTP 基本认证。
  10. RequestCacheAwareFilter

    • 管理请求缓存与重定向。
  11. SecurityContextHolderAwareRequestFilter

    • 使 SecurityContext 可用作请求属性。
  12. AnonymousAuthenticationFilter

    • 为未身份验证的请求创建匿名身份认证。
  13. SessionManagementFilter

    • 管理和保护会话。
  14. ExceptionTranslationFilter

    • 转换和处理安全异常。
  15. FilterSecurityInterceptor

    • 执行访问控制决策。

调整过滤器顺序

你可以使用 HttpSecurity 提供的方法将自定义过滤器插入到过滤器链的特定位置:

  • addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter)

    • 将自定义过滤器添加到指定过滤器之前。
  • addFilterAfter(Filter filter, Class<? extends Filter> afterFilter)

    • 将自定义过滤器添加到指定过滤器之后。
  • addFilterAt(Filter filter, Class<? extends Filter> atFilter)

    • 将自定义过滤器添加到指定过滤器的确切位置。
  • /**
     * 配置 JWT 过滤器
     */
    http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    
    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

    #### 示例:自定义过滤器并调整顺序

    假设我们需要自定义一个日志记录的过滤器,并将其放在 `UsernamePasswordAuthenticationFilter` 之前:

    ##### 1. 创建自定义过滤器

    ```java
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpFilter;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;

    public class LoggingFilter extends HttpFilter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    super.init(filterConfig);
    // 初始化代码
    }

    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    System.out.println("Request URI: " + request.getRequestURI());
    chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
    // 清理代码
    }
    }
2. 注册自定义过滤器并调整顺序

WebSecurityConfigurerAdapter 配置类中注册自定义过滤器:

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
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.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(new LoggingFilter(), UsernamePasswordAuthenticationFilter.class) // 在 UsernamePasswordAuthenticationFilter 之前添加自定义过滤器
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}

查看过滤器链顺序

在开发和调试过程中,你可以启用 Spring Security 的调试日志,这样就能查看每个过滤器的执行顺序:

1
logging.level.org.springframework.security=DEBUG

可以在 application.properties 文件中添加上述配置。当你启动应用时,Spring Security 将打印每个过滤器的执行顺序,这有助于你确认自定义过滤器的位置是否正确。

总结

Spring Security 的过滤器链的顺序非常重要,因为每个过滤器依赖于前面的过滤器执行特定的功能,才能实现整体的安全控制。你可以通过 HttpSecurity 配置类来调整过滤器的顺序,并且可以使用调试日志来检查当前的过滤器链顺序。合理配置过滤器链顺序对于构建安全、高效的应用程序至关重要。

RBAC 权限模型

​ SpringSecurity:安全认证框架。包含:登录认证、权限授予、权限验证。

​ 基于 “ 用户 => 角色 => 权限 ” 的 权限验证设计模型。

​ 登录认证:用户模型

​ 权限授予:权限授予角色模型

​ 权限验证:登录的用户模型中的角色,是否含有权限授予的角色模型

rhythm_security_db

securityConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24


@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启注解权限校验,否则无法识别@PreAuthorize注解
public class SecurityConfig extends WebSecurityConfigurerAdapter {

...

http.headers().frameOptions().disable() // 允许iframe嵌套

.and()
.csrf().disable()
// 自定义异常处理
.exceptionHandling()
// 如果请求中未包含有效的 JWT,JwtRequestFilter 不会设置认证,CustomAuthenticationEntryPoint 将截获并返回 401 状态码
.authenticationEntryPoint(customAuthenticationEntryPoint)
// 如果请求中包含有效的 JWT,但用户没有所需的 ADMIN 权限,权限检查会失败,CustomAccessDeniedHandler 将截获并返回 403 状态码
// 在 Spring Security 配置中,如果你配置了多个 .accessDeniedHandler(),只会使用最后一个配置的 AccessDeniedHandler
.accessDeniedHandler(customAccessDeniedHandler)

...


1
2
3
4
5
6

@PreAuthorize("hasRole('系统管理员')")
@ResponseBody
@ApiOperation("根据ad_id查询对应信息")
@GetMapping("/ads/{ad_id}")

testCase

image-20240802175018069

image-20240802171322406

image-20240802175218287

image-20240802171459021

image-20240802175336554

登录认证:用户模型

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
@PostMapping("/springSecurity/login")
@ResponseBody
public ResultData loginBySpringSecurity(@RequestBody AdminLoginBo adminLoginBo) {

// 创建一个 UsernamePasswordAuthenticationToken 对象
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// 调用 authenticationManager 的 authenticate 方法来处理认证逻辑。
// 它会调用配置的 UserDetailsService 和 PasswordEncoder 来验证用户的身份信息。
// 认证成功后,它会返回一个 Authentication 对象,包含了已认证用户的详细信息和权限
Authentication authentication = authenticationManager.authenticate(authRequest);

// 设置认证信息到SecurityContext,以便当前请求可以使用
SecurityContextHolder.getContext().setAuthentication(authentication);

...

// 生成 JWT
// 代码中演示的JWT只存放了用户名,实际开发中根据需要存储,但不建议存敏感信息(比如密码), 因为JWT只能防止篡改字符串,不能防止解密这个字符串
String token = JwtTokenUtil.generateToken(username);

// 将签发的令牌,存入Redis中。拼接上Authorization的策略(Bearer Token)前缀。
redisUtil.set( "Bearer " + token , username , 60 * 24 );


// 判断 登录是否成功
if( token == null ){
return ResultData.fail(500,"请登录");
}else{
return ResultData.success(token);
}

}


public class JwtTokenUtil {

private static final String SECRET_KEY = "";

// 生成令牌
public static String generateToken(String username) {
System.out.println("SECRET_KEY: " + SECRET_KEY);
Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
return JWT.create()
.withSubject(username)
.withIssuedAt(new Date())
.withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 24)) // 24min有效期
.sign(algorithm);
}

image-20240801113738597

image-20240801114733415

image-20240801114140898

image-20240801114458817

权限授予:权限授予角色模型

基于方法的动态权限

(详见securityConfig)

image-20240802171322406

image-20240802171459021

1
2
3
4
5
6

@PreAuthorize("hasRole('系统管理员')")
@ResponseBody
@ApiOperation("根据ad_id查询对应信息")
@GetMapping("/ads/{ad_id}")

权限验证:登录的用户模型中的角色,是否含有权限授予的角色模型

基于url路径的动态权限

image-20240802211924643

image-20240802214137505

image-20240802213802297

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
53
54



-- operate
-- permission_operate
-- role_permission
-- role

-- 1: 音乐模块 音乐管理模块
-- 2: 音乐模块

SELECT
d.*
FROM
`operate` a
INNER JOIN `permission_operate` b on a.operate_id = b.operate_id
INNER JOIN `role_permission` c on b.permission_id = c.permission_id
INNER JOIN `role` d on c.role_id = d.role_id
WHERE
a.operate_url = '/api/categories/add'; -- roleList角色列表 系统管理员


SELECT
d.*
FROM
`operate` a
INNER JOIN `permission_operate` b on a.operate_id = b.operate_id
INNER JOIN `role_permission` c on b.permission_id = c.permission_id
INNER JOIN `role` d on c.role_id = d.role_id
WHERE
a.operate_url = '/api/music/add'; -- roleList角色列表 系统管理员 用户


SELECT
d.*
FROM
`operate` a
INNER JOIN `permission_operate` b on a.operate_id = b.operate_id
INNER JOIN `role_permission` c on b.permission_id = c.permission_id
INNER JOIN `role` d on c.role_id = d.role_id
WHERE
a.operate_url = '/api/user/1'; -- null roleList角色列表为空,即该url不需要权限认证


SELECT
d.*
FROM
`operate` a
INNER JOIN `permission_operate` b on a.operate_id = b.operate_id
INNER JOIN `role_permission` c on b.permission_id = c.permission_id
INNER JOIN `role` d on c.role_id = d.role_id
WHERE
a.operate_url = '/api/categories/queryAll' -- null roleList角色列表 系统管理员,即该url需要权限认证

image-20240802213957282

image-20240802213901209

image-20240802213914106

image-20240802213926289

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19


public class SecurityConfig extends WebSecurityConfigurerAdapter {

...

.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setSecurityMetadataSource(permissionAuthority); // 自定义SecurityMetadataSource实现复杂的安全需求(即基于数据库)
o.setAccessDecisionManager(permissionValid); // 自定义AccessDecisionManager实现复杂的安全需求(即基于数据库)
return o;
}
})

...

}

PermissionAuthority implements FilterInvocationSecurityMetadataSource

首先自定义FilterInvocationSecurityMetadataSource 通过FilterInvocationSecurityMetadataSource接口中的getAttributes方法来确定一个请求需要哪些角色,

遍历过程中获取当前请求的URL所需要的角色信息并返回。如果当前请求的URL在资源表中不存在相应的模式,

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
@Component
public class PermissionAuthority implements FilterInvocationSecurityMetadataSource {

@Resource
private RoleMapper roleMapper;

@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {

// 获取请求的URL
String requestUrl = ( (FilterInvocation) object ).getRequestUrl();

// 根据URL获取可以访问角色列表
List<Role> roleList = roleMapper.getListByOperateUrl(requestUrl);

// 判断角色列表是否为空
if( !roleList.isEmpty() && roleList.size() > 0 ){
// 准备数组存放授权列表
String[] roles = new String[roleList.size()];
// 循环角色列表中的每一个元素
for( int i = 0 ; i <= roles.length - 1; i++ ){
// 将当前循环到角色存放到授权列表中
roles[i] = roleList.get(i).getRoleName();
}
// 返回授权列表
return SecurityConfig.createList(roles);
}
// 避免返回null(任何角色都能访问)
return SecurityConfig.createList("PublicPermission");
}

@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}

@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}

PermissionValid implements AccessDecisionManager

当一个请求走完FilterInvocationSecurityMetadataSource中的getAttributes方法后,接下来就会来到AccessDecisionManager类中进行角色信息的比对,

重写decide方法,在该方法中判断当前登录的用户是否具备当前请求URL所需要的角色信息,如果不具备,就抛出AccessDeniedException异常,否则不做任何事即可

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
@Component
public class PermissionValid implements AccessDecisionManager {

@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {

// 迭代遍历当前请求的URL授权的所有角色
for( ConfigAttribute attribute : configAttributes ){

// 判断当前账户是否为空
if (authentication == null) {
throw new AccessDeniedException("权限认证失败!");
}

// 获取当前请求的授权角色
String needRole = attribute.getAttribute();

System.out.println( "-------开始认证-------");
// 判断是否是默认权限
if ("PublicPermission".equals(needRole)) {
if (authentication instanceof AnonymousAuthenticationToken) {
throw new BadCredentialsException("未登录");
} else
return;
}
// 获取当前账户所拥有的角色
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
// 迭代遍历当前账户的每一个角色
for (GrantedAuthority authority : authorities) {
// 判断当前账户的角色是否和当前循环到的授权角色相同
if (authority.getAuthority().equals(needRole)) {
return;
}
}
}
// 认证失败
throw new AccessDeniedException("权限认证失败!");
}

@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}

@Override
public boolean supports(Class<?> clazz) {
return true;
}
}

spring security 登录认证 权限认证 前后端分离

在前后端分离架构中,Spring Security的配置需要调整以支持基于JWT(JSON Web Token)的认证和授权。这种方式可以避免传统的会话(session)维护,通过前端携带JWT令牌进行后端API的访问和权限控制。

登录校验流程(用户密码认证生成认证信息):

image-20240710165603847

image-20240802170124925

http://127.0.0.1:8001/api/springSecurity/login

  • ——-JwtRequestFilter, 开始jwt校验——- Ignoring filter for path: /api/springSecurity/login
  • ——-PermissionAuthority implements FilterInvocationSecurityMetadataSource——-
  • ——-PermissionValid implements AccessDecisionManager——-
  • ————— 运行/springSecurity/login —————
1
2
Authentication authentication = authenticationManager.authenticate(authRequest);  // 返回一个对象 authentication,包含了已认证(authentication)用户的详细信息和权限(authorization)
SecurityContextHolder.getContext().setAuthentication(authentication);

/springSecurity/login

SecurityContextHolder.getContext().setAuthentication(authentication); 确实是在登录时手动设置了认证对象。这个操作通常会在用户登录成功时进行,用于确保用户的认证信息正确保存到当前的安全上下文中。

不过,这样的设置主要是对一次请求有效。如果你想在前后端分离的场景下持久化状态,在每次请求中都自动完成认证、授权操作(即JwtRequestFilter.java),通常需要一个无状态的机制,如基于 JWT(JSON Web Token)的认证方式。

在用户成功登录后,通过 SecurityContextHolder.getContext().setAuthentication(authentication) 手动设置认证对象,这在仅当前请求中生效。这意味着,当请求结束时,安全上下文会被清空,认证信息不会自动传播到后续的请求中。

在前后端分离的场景中,如果你希望在后续的每个请求中都能自动进行认证,你需要使用无状态的认证机制,例如 JSON Web Token (JWT) 或者别的形式的令牌认证。这种机制通过在每个请求中携带令牌,后端过滤器拦截请求并验证令牌,从而恢复认证信息。

1
2
3
4
5
6
7
8

// JwtRequestFilter.java

* 后端通过一个过滤器来拦截所有请求,验证 JWT 并解析其中的信息,然后将解析得到的 Authentication 对象保存到安全上下文中
*
* 后端通过一个过滤器来拦截所有请求,验证 JWT 并恢复认证信息。


testCase

image-20240719130632838

1
2
3
4
5
6
@PreAuthorize("hasRole('系统管理员')")
@ResponseBody
@ApiOperation("根据ad_id查询对应信息")
@GetMapping("/ads/{ad_id}")

http://127.0.0.1:8001/api/ads/1

image-20240719144350897

1
2
3
4
5
6
@PreAuthorize("hasRole('用户')")
@ResponseBody
@ApiOperation("查询获取当前用户收藏的音乐")
@GetMapping("/user/{user_id}")

http://127.0.0.1:8001/api/user/1

image-20240719144511754

jwt认证(jwt认证恢复认证信息)

  • SecurityContextHolder:上下文管理对象,用来在程序任何地方获取SecurityContext
  • SeucirtyContext:上下文对象,用来获取Authentication
  • Authentication:存储了认证信息,代表当前登录用户
    • Principal:用户信息,没有认证时一般是用户名,认证后一般是用户对象
    • Credentials:用户凭证,一般是密码
    • Authorities:用户权限

img

我们通过 SecurityContext 来获取Authentication,

[!CAUTION]

这种在一个线程中横跨若干方法调用,需要传递的对象,我们通常称之为上下文(Context)。上下文对象是非常有必要的,否则你每个方法都得额外增加一个参数接收对象,实在太麻烦了。

这个上下文对象则是交由 SecurityContextHolder 进行管理,

SecurityContextHolder源码原理非常简单,就是和我们之前实现的上下文对象一样,使用ThreadLocal来保证一个线程中传递同一个对象

在用户成功登录后,通过 SecurityContextHolder.getContext().setAuthentication(authentication) 手动设置认证对象,这在仅当前请求中生效。这意味着,当请求结束时,安全上下文会被清空,认证信息不会自动传播到后续的请求中。

在前后端分离的场景中,如果你希望在后续的每个请求中都能自动进行认证,你需要使用无状态的认证机制,例如 JSON Web Token (JWT) 或者别的形式的令牌认证。这种机制通过在每个请求中携带令牌,后端过滤器拦截请求并验证令牌,从而恢复认证信息。

1
2
3
4
5
6
7
// JwtRequestFilter.java

* 后端通过一个过滤器来拦截所有请求,验证 JWT 并解析其中的信息,然后将解析得到的 Authentication 对象保存到安全上下文中
*
* 后端通过一个过滤器来拦截所有请求,验证 JWT 并恢复认证信息。


在 JWT 认证中,用户首次登录依然需要用户名和密码,但之后的请求只需提供 JWT,因为 JWT 包含了足够的用户身份信息(如用户 ID 和权限),可以通过解析和验证恢复用户认证状态。

具体步骤:

  1. 最小化差异:在生成 JWT 时,只包含必要的信息,如用户 ID、用户名和权限。在验证 JWT 时,使用这些信息重新构建一个 Authentication 对象。
  2. 使用 UserDetailsService:在验证 JWT 后,可以使用 UserDetailsService 重新加载完整的用户信息,以确保信息的一致性和最新性。
  3. 定期刷新:实施 JWT 刷新机制,定期更新 JWT,以确保信息的时效性。
  • 基于会话:使用会话 ID 从服务器存储中恢复用户信息,适合简单的单体应用。
  • 基于 JWT:每个请求包含 JWT,通过解析和验证 JWT 恢复用户信息,适用于复杂的分布式系统,不需要服务器端存储会话信息。

动态加载用户菜单

image-20240802225159730

image-20240802225356601

用户菜单就是用户登录成功后首页显示的菜单。这个菜单数据是根据用户的角色动态加载的,即不同预设角色的用户登录成功后看到的菜单不同。

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

/**
*
* @TableName admin
*/
@TableName(value ="admin")
@Data
public class Admin implements Serializable, UserDetails {
/**
*
*/
@TableId(type = IdType.AUTO)
private Integer adminId;

/**
*
*/
private String adminName;

/**
*
*/
private String adminPass;

/**
*
*/
private String adminNickname;

@TableField(exist = false)
private static final long serialVersionUID = 1L;

// Association 组合关系
// Collection 聚合关系
private List<Role> roleList; // 登录用户的角色集合
private List<Menu> menuList; // 登录用户的菜单集合

// UserDetails 用户类接口中的抽象方法
/**
* 获取当前用户的授权集合 => 获取当前用户拥有的角色列表
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return getRoleList();
}

@Override
public String getPassword() {
return getAdminPass();
}

@Override
public String getUsername() {
return getAdminName();
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13

@Mapper
public interface AdminMapper extends BaseMapper<Admin> {

/**
* 根据账号username查询一个含有角色、菜单信息的登录用户对象
* @param username
* @return 一个含有角色、菜单信息的登录用户实体模型对象
*/
Admin getOneWithRolelistMenulistByName(String username);

}

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

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.test.mapper.AdminMapper">

<resultMap id="BaseResultMap" type="com.test.bean.po.Admin">
<id property="adminId" column="admin_id" jdbcType="INTEGER"/>
<result property="adminName" column="admin_name" jdbcType="VARCHAR"/>
<result property="adminPass" column="admin_pass" jdbcType="VARCHAR"/>
<result property="adminNickname" column="admin_nickname" jdbcType="VARCHAR"/>
</resultMap>

<!--含有角色信息的管理员 结果映射方案-->
<resultMap id="AdminWithRoleResultMap" type="com.test.bean.po.Admin">
<id property="adminId" column="admin_id" jdbcType="INTEGER"/>
<result property="adminName" column="admin_name" jdbcType="VARCHAR"/>
<result property="adminPass" column="admin_pass" jdbcType="VARCHAR"/>
<result property="adminNickname" column="admin_nickname" jdbcType="VARCHAR"/>
<collection property="roleList"
ofType="com.test.bean.po.Role"
column="admin_id"
select="com.test.mapper.RoleMapper.selectListByAdminId"/>
</resultMap>

<!--含有角色、菜单信息的管理员 结果映射方案-->
<resultMap id="AdminWithRoleAndMenuResultMap" type="com.test.bean.po.Admin" extends="AdminWithRoleResultMap">
<collection property="menuList"
ofType="com.test.bean.po.Menu"
column="admin_id"
select="com.test.mapper.MenuMapper.selectListByAdminId"/>
</resultMap>

<sql id="Base_Column_List">
admin_id,admin_name,admin_pass,
admin_nickname
</sql>

<select id="getOneWithRolelistMenulistByName" resultMap="AdminWithRoleAndMenuResultMap">
select *
from rhythm.admin
where admin_name =#{username}
</select>

</mapper>

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

@Service
public class AdminServiceImpl extends ServiceImpl<AdminMapper, Admin>
implements AdminService {

@Resource
private AdminMapper adminMapper;

@Autowired
private PasswordEncoder passwordEncoder;


@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
System.out.println("开始查询数据库... ");

// 调用数据访问层 根据账号名称 获取 含有角色列表、菜单列表的管理员对象 resultMap/collection 类的聚合关系
Admin admin = adminMapper.getOneWithRolelistMenulistByName( username );

System.out.println("根据username查询到的用户账号信息: " + admin);
if( admin == null ){
System.out.println("账户名称或密码错误!请重新填写!");
throw new UsernameNotFoundException("账户名称或密码错误!请重新填写!");
}
return admin;
}

}

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

/**
*
* 实现一个过滤器,以在每个请求中验证 JWT 令牌
*
*/
@Component
public class JwtRequestFilter extends OncePerRequestFilter {

...

// 通过用户名获取用户数据, 返回 UserDetails 对象, 表示用户的核心信息 (admin对象)
UserDetails userDetails = this.adminService.loadUserByUsername(username);
// 创建包含权限的 UsernamePasswordAuthenticationToken 对象
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, adminService.loadUserByUsername(username).getAuthorities());
// 使用 WebAuthenticationDetailsSource 设置请求的详细信息(如 IP 地址、会话 ID 等)
// WebAuthenticationDetailsSource 是一个工具类,用于从 HttpServletRequest 中提取详细信息,
// 并将其添加到 WebAuthenticationDetails 对象中。这些详细信息通常包括客户端的 IP 地址和会话 ID。
//
// 我们构建 UsernamePasswordAuthenticationToken 对象来存储认证(authentication)信息
// 我们可以使用 WebAuthenticationDetailsSource 为构建的 UsernamePasswordAuthenticationToken 添加额外的请求的上下文信息。这不仅有助于审计和监控,还能帮助我们实现一些特定的安全需求,例如防止多次登录攻击。

// 将详细信息设置到 UsernamePasswordAuthenticationToken 认证对象中,并更新 SecurityContext
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);

...

}

1
2
3
4
5
6
7
8
9
10
11
12
13

@GetMapping( value = {"/getMenuList"} )
@ResponseBody
@ApiOperation("返回 当前登录用户 登录认证authentication信息")
public ResultData getMenuList( Authentication authentication ) {

System.out.println( "Authentication = " + authentication);
Admin admin = (Admin) authentication.getPrincipal();
System.out.println( "admin = " + admin);

return ResultData.success(((Admin) authentication.getPrincipal()).getMenuList());
}

image-20240802220950111

参考:

https://www.cnblogs.com/meer/p/17652916.html

https://zhuanlan.zhihu.com/p/342755411?utm_medium=social&utm_oi=1343915562263547904

https://zhuanlan.zhihu.com/p/342746910

https://zhuanlan.zhihu.com/p/342744060

相关问题

There was an unexpected error (type=Internal Server Error, status=500). Request method ‘POST’ not supported

1
2
3
4
5
.and()
.csrf()
.disable()
.exceptionHandling()
.accessDeniedHandler(permissionErrorHandle);

默认情况下,Spring Security 启用 CSRF 保护。此处禁用了 CSRF 保护(虽然不推荐),为了保持安全性,请在生产环境启用 CSRF 保护并正确配置 CSRF 令牌。

There is no PasswordEncoder mapped for the id “null”
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id “null”
at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:250)

确保所有的密码编码和解码使用同一个 BCryptPasswordEncoder 实例

1
2
3
4
5
6
7
8
9
10
.inMemoryAuthentication()

.withUser("user")
.password(new BCryptPasswordEncoder().encode("user"))
.roles("USER")

.and()
.withUser("admin")
.password(new BCryptPasswordEncoder().encode("admin_password"))
.roles("ADMIN");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.inMemoryAuthentication()

.withUser("user")
.password(passwordEncoder().encode("user"))
.roles("USER")

.and()
.withUser("admin")
.password(passwordEncoder().encode("admin_password"))
.roles("ADMIN");

}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

org.thymeleaf.exceptions.TemplateInputException: Error resolving template [hello], template might not exist or might not be accessible by any of the configured Template Resolvers

1
2
3
4
5
6
@Controller

@GetMapping("/hello")
public String hello() {
return "hello"; // 返回视图名为 hello 的模板
}
1
2
3
4
5
6
7
@Controller

@GetMapping("/hello")
@ResponseBody // Ensures this method returns plain text
public String hello() {
return "hello"; // 返回hello字符串 不返回视图
}

过滤器 拦截器

public class PerformanceFilter implements Filter {

public class PerformanceInterceptor implements HandlerInterceptor {

过滤器(Filters)和拦截器(Interceptors)是 Java Web 开发中常用的两种机制,用于在请求到达控制器之前或响应返回客户端之前进行预处理或后处理。虽然它们都可以用于类似的目的,但在实现机制和应用场景上有所不同。

过滤器(Filters)

过滤器是 Servlet API 的一部分,主要用于对 HTTP 请求和响应进行预处理和后处理。过滤器可以拦截和修改请求和响应,但不会修改实际的控制器处理逻辑。

使用场景
  • 检查或修改请求参数
  • 进行身份验证和授权
  • 记录日志
  • 数据压缩或加密
示例代码

下面是一个简单的过滤器,它记录每个请求的处理时间:

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
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

@WebFilter(urlPatterns = "/*")
public class PerformanceFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化代码
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
long startTime = System.currentTimeMillis();
chain.doFilter(request, response);
long endTime = System.currentTimeMillis();
System.out.println("Request processed in " + (endTime - startTime) + " ms");
}

@Override
public void destroy() {
// 销毁代码
}
}
配置

可以使用 @WebFilter 注解或在 web.xml 文件中配置过滤器。上面的例子使用的是 @WebFilter 注解。

拦截器(Interceptors)

拦截器通常是框架(如 Spring MVC)提供的机制,不是 Servlet API 的一部分。它用于在控制器方法调用之前和之后进行处理。拦截器比过滤器更强大,可以访问更多的信息,如控制器方法参数,返回值等。

使用场景
  • 用户认证和授权
  • 日志记录和分析
  • 统一异常处理
  • 数据格式转换
示例代码

以下是一个简单的 Spring MVC 拦截器,用于记录每个请求的处理时间:

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
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class PerformanceInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
request.setAttribute("startTime", System.currentTimeMillis());
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
long startTime = (Long) request.getAttribute("startTime");
long endTime = System.currentTimeMillis();
System.out.println("Request processed in " + (endTime - startTime) + " ms");
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// 完成处理
}
}
配置

在 Spring MVC 中,你需要将拦截器注册到 WebMvcConfigurer

1
2
3
4
5
6
7
8
9
10
11
12
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new PerformanceInterceptor()).addPathPatterns("/**");
}
}
主要区别
  1. 实现机制

    • 过滤器是 Servlet API 的一部分,可以应用于所有的 Servlet 请求。
    • 拦截器通常是框架(如 Spring MVC)提供的功能,主要用于控制器方法的调用。
  2. 使用范围

    • 过滤器可以应用于整个应用程序,无论是静态资源还是动态请求。
    • 拦截器通常只处理控制器方法的请求。
  3. 访问权限

    • 过滤器只能访问请求和响应对象,无法访问控制器方法的参数和返回值。
    • 拦截器可以访问控制器方法的参数和返回值,具有更高的灵活性。
  4. 配置方式

    • 过滤器可以在 web.xml 文件中配置,也可以使用注解。
    • 拦截器需要在框架配置文件中注册或使用代码进行配置。
总结

过滤器和拦截器都是预处理和后处理请求的有效机制,各有优缺点和适用的场景。在选择使用哪种机制时,可以根据需要处理的内容和应用程序的具体需求来选择。通常,过滤器适用于全局请求处理,而拦截器更适用于控制器级别的请求处理。

spring security 使用的过滤器还是拦截器

Spring Security 是一个强大的安全框架,用于保护 Java 应用程序。它主要使用过滤器(Filters)来实现安全功能,而不是拦截器(Interceptors)。不过,它也提供了一些拦截器扩展功能,可以在某些场景下与 Spring MVC 拦截器配合使用。

Spring Security 的过滤器

Spring Security 主要通过一系列的过滤器来处理安全性。这些过滤器被组织成一个过滤器链,这个链会在每个 HTTP 请求到达实际处理代码之前进行处理。常见的过滤器有:

  • **SecurityContextPersistenceFilter**:管理 SecurityContext 的生命周期。
  • **UsernamePasswordAuthenticationFilter**:处理基于表单的登录。
  • **BasicAuthenticationFilter**:处理 HTTP 基本认证。
  • **ExceptionTranslationFilter**:捕获安全异常,将其转化为符合客户端期望的响应。
  • **FilterSecurityInterceptor**:最终进行访问控制决策。
配置 Spring Security 过滤器链

Spring Security 的过滤器链默认通过 DelegatingFilterProxy 进行配置,通常可以在 Spring 的配置类中自定义和扩展。

XML 配置

如果你使用 XML 配置,可以通过 web.xml 配置 DelegatingFilterProxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<http>
<!-- 配置安全规则 -->
</http>
</beans:beans>
Java 配置

使用 Java 配置,通过继承 WebSecurityConfigurerAdapter 并重写其方法来自定义安全配置:

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
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.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}
Spring Security 拦截器

Spring Security 也提供了对拦截器的支持,比如方法级别的安全性(Method Security),可以通过注解来实现,如 @PreAuthorize@PostAuthorize

方法级别安全配置

####### 启用方法级别的安全性

通过在配置类中启用方法级别的安全性:

1
2
3
4
5
6
7
8
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
// 配置方法级别安全性
}

####### 使用注解进行方法级别的安全控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;

@Service
public class MyService {

@PreAuthorize("hasRole('ROLE_USER')")
public void userMethod() {
// 只有角色为 ROLE_USER 的用户才能访问此方法
}

@PreAuthorize("hasRole('ROLE_ADMIN')")
public void adminMethod() {
// 只有角色为 ROLE_ADMIN 的用户才能访问此方法
}
}
主要区别
  1. 使用场景

    • 过滤器:Spring Security 主要使用过滤器来处理整体的请求/响应安全。
    • 拦截器:拦截器主要用于更多细粒度的控制,如方法级别的安全性。
  2. 实现机制

    • 过滤器:是 Servlet 规范的一部分,负责在请求进入和响应离开 Web 容器时进行处理。
    • 拦截器:通常是通过框架提供的机制实现,如 Spring MVC 的HandlerInterceptor。
  3. 配置方式

    • 过滤器:通过 DelegatingFilterProxyspringSecurityFilterChain 进行配置。
    • 拦截器:通过注解 @EnableGlobalMethodSecurity 和相关安全注解进行配置。
总结

Spring Security 主要通过过滤器链实现请求和响应的安全处理,但也提供了方法级别的安全控制支持,允许你通过拦截器和注解来进行细粒度的安全控制。过滤器用于全局的 HTTP 请求安全管理,而拦截器则用于更具体的方法级别的权限控制。

handler 与 (handler)adapter 是一样的吗 静态代理 动态代理

不,HandlerAdapter 在Spring MVC框架中是不一样的概念,各自扮演不同的角色。具体来说:

Handler(处理器)
定义

Handler是负责处理HTTP请求的对象,它包含了具体的业务逻辑。这是MVC架构中的Controller或其他类,它处理用户请求并生成相应的响应。

例子

在Spring MVC中,最常见的Handler是带有@Controller@RequestMapping注解的类或方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class MyController {

@RequestMapping("/hello")
@ResponseBody
public String handleRequest() {
return "Hello, World!";
}
}

这里的MyController类中的handleRequest方法就是一个Handler。

(handler)Adapter(适配器)
定义

Adapter(处理器适配器)是一个接口,它的职责是将各种不同类型的Handler适配到Spring MVC的处理流程中,用于解耦DispatcherServlet和实际执行请求的Handler。HandlerAdapter负责调用具体的Handler方法,并处理其返回值。

例子

Spring MVC提供了多种内置的HandlerAdapter,如RequestMappingHandlerAdapter用于@RequestMapping注解的方法,HttpRequestHandlerAdapter用于HttpRequestHandler接口的实现类。

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
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CustomHandlerAdapter implements HandlerAdapter {

@Override
public boolean supports(Object handler) {
return handler instanceof CustomHandler;
}

@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
CustomHandler customHandler = (CustomHandler) handler;
String viewName = customHandler.handleRequest(request, response);
return new ModelAndView(viewName);
}

@Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}
核心区别
  1. 职责不同

    • Handler:负责处理具体的HTTP请求并生成响应。
    • HandlerAdapter:负责将Handler适配到Spring MVC的标准处理流程中,处理Handler的调用和返回值。
  2. 层次不同

    • Handler是业务逻辑层的一部分,具体执行和响应请求。
    • HandlerAdapter是框架层的一部分,帮助DispatcherServlet调用Handler
  3. 方式不同

    • Handler可以是任何符合业务需求的Java对象,如Controller类。
    • HandlerAdapter通常是Spring框架内置的,也可以自行实现以自定义执行逻辑。
工作流程

(servlet 9大组件) (servlet 9大类)

( DispatcherServlet - HandlerMapping - HandlerAdapter - ViewResolver )

image-20240703120729712

image-20240703121032649

image-20240703121229522

  1. 收到请求DispatcherServlet接收到一个HTTP请求。
  2. 查找处理器:通过HandlerMapping找到这个请求要处理的Handler
  3. 选择适配器
    • DispatcherServlet查找一个合适的HandlerAdapter来适配找到的Handler
  4. 调用处理器
    • HandlerAdapter调用Handler的具体业务方法,生成处理结果(如ModelAndView对象)。
  5. 视图渲染
    • DispatcherServlet使用ViewResolver解析视图并渲染,最终返回HTTP响应。
总结
  • Handler:具体处理HTTP请求的组件,包含业务逻辑。
  • HandlerAdapter:适配器,负责将不同类型的Handler适配到统一的处理流程中,并调用Handler的方法。

它们在职责、层次和实现方式上都有所不同,但共同协作完成Spring MVC的请求处理工作。通过理解这两者的区别,可以更好地理解Spring MVC的工作机制,并根据需要进行扩展和自定义。

处理器适配器(HandlerAdapter)、静态代理和动态代理在Spring框架中都有其各自的用例和应用方式。下面将详细解释这些概念并展示它们的示例。

HandlerAdapter

HandlerAdapter 是Spring MVC框架中的一个接口,它用于将HTTP请求映射到具体的处理器方法。处理器适配器使得DispatcherServlet可以调用不同类型的处理器(如控制器、HttpRequestHandlers等)。

示例
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
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyCustomHandlerAdapter implements HandlerAdapter {

@Override
public boolean supports(Object handler) {
return handler instanceof MyCustomHandler;
}

@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
MyCustomHandler myHandler = (MyCustomHandler) handler;
String viewName = myHandler.handleRequest(request, response);
return new ModelAndView(viewName);
}

@Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}

class MyCustomHandler {
public String handleRequest(HttpServletRequest request, HttpServletResponse response) {
return "customView";
}
}
静态代理

静态代理是指由程序员创建或通过工具生成代理类的方式。在静态代理中,代理类与委托类在编译时就已经确定。静态代理主要被用于横切关注点(如日志记录、权限检查等)。

示例
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
interface GreetingService {
void sayHello();
}

class GreetingServiceImpl implements GreetingService {
@Override
public void sayHello() {
System.out.println("Hello, World!");
}
}

*-------------------------------------------*
class GreetingServiceProxy implements GreetingService {
private GreetingServiceImpl greetingService;

public GreetingServiceProxy(GreetingServiceImpl greetingService) {
this.greetingService = greetingService;
}

@Override
public void sayHello() {
// 添加额外的逻辑
System.out.println("Before execution");
greetingService.sayHello();
System.out.println("After execution");
}
}

public class StaticProxyDemo {
public static void main(String[] args) {
GreetingService service = new GreetingServiceProxy(new GreetingServiceImpl());
service.sayHello();
}
}
动态代理

动态代理是指代理类在运行时创建的。这使得我们可以在运行时指定行为,而不需要提前知道具体的代理类。Java中的动态代理通常使用java.lang.reflect.ProxyInvocationHandler接口。

示例
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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface GreetingService {
void sayHello();
}

class GreetingServiceImpl implements GreetingService {
@Override
public void sayHello() {
System.out.println("Hello, World!");
}
}

*-------------------------------------------*
class GreetingServiceInvocationHandler implements InvocationHandler {
private Object target;

public GreetingServiceInvocationHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 添加额外的逻辑
System.out.println("Before execution");
Object result = method.invoke(target, args);
System.out.println("After execution");
return result;
}
}

public class DynamicProxyDemo {
public static void main(String[] args) {
GreetingService target = new GreetingServiceImpl();
GreetingService proxy = (GreetingService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new GreetingServiceInvocationHandler(target)
);
proxy.sayHello();
}
}
对比和总结
  1. 相似点:
    • 都是为了增强或适配某种功能
    • 都在原有代码和新功能之间起到了桥梁作用
  2. 不同点:
    • Handler Adapter 主要用于适配不同类型的处理器,使其能够被 DispatcherServlet 统一调用
    • 代理模式(无论是静态还是动态)主要用于在不修改原有代码的情况下,增加新的功能或控制对原有功能的访问
  3. 在 Spring 中的应用:
    • Handler Adapter 主要用于 Spring MVC 的请求处理流程
    • 代理模式在 Spring 中广泛应用,特别是在 AOP(面向切面编程)中。Spring 使用动态代理来实现诸如事务管理、日志记录等横切关注点
  • HandlerAdapter:用于Spring MVC框架中,帮助将HTTP请求映射到具体的处理器方法。主要用于MVC的请求处理流程。
  • 静态代理:在编译时固定的代理类,通过实现同样的接口或继承同样的类来创建。主要用于提前知道的场景,添加横切关注点。
  • 动态代理:在运行时创建的代理类,通常使用Java反射机制。主要用于不确定具体代理类,或需要在运行时动态处理方法调用的场景。

总的来说,Handler Adapter 更多地是一种设计模式的应用(适配器模式),而代理模式(包括静态代理和动态代理)则是一种更通用的设计模式,在 Spring 框架中有着更广泛的应用。通过理解这些概念及其应用场景,开发者可以更好地利用Spring框架及其强大的扩展机制来实现复杂的业务需求。

动态代理在Spring MVC中的应用

在Spring MVC中,可以使用动态代理来增强HandlerAdapter的功能,比如在请求处理前后添加自定义逻辑。下面是一个示例,展示如何使用动态代理来增强HandlerAdapter

1. 定义一个HandlerAdapter的动态代理处理器
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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class HandlerAdapterProxy implements InvocationHandler {
private final Object target;

public HandlerAdapterProxy(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("handle".equals(method.getName())) {
HttpServletRequest request = (HttpServletRequest) args[0];
HttpServletResponse response = (HttpServletResponse) args[1];
Object handler = args[2];

// 在执行处理器方法之前的逻辑
System.out.println("Before handling request");

Object result = method.invoke(target, args);

// 在执行处理器方法之后的逻辑
System.out.println("After handling request");

return result;
} else {
return method.invoke(target, args);
}
}
}
2. 创建代理实例并使用它

假设我们有一个自定义的HandlerAdapter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

==> request ==handlerMapping ==> handlerAdapter(CustomHandlerAdapter) ==> resolverView

import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.ModelAndView;

public class CustomHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return handler instanceof MyCustomHandler;
}

@Override
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 自定义处理逻辑
return new ModelAndView("customView");
}

@Override
public long getLastModified(HttpServletRequest request, Object handler) {
return -1;
}
}
3. **配置DispatcherServlet来使用代理HandlerAdapter**:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Proxy;

@Configuration
public class WebConfig {
@Bean
public HandlerAdapter customHandlerAdapter() {
CustomHandlerAdapter originalAdapter = new CustomHandlerAdapter();
return (HandlerAdapter) Proxy.newProxyInstance(
originalAdapter.getClass().getClassLoader(),
new Class[]{HandlerAdapter.class},
new HandlerAdapterProxy(originalAdapter)
);
}
}

通过这样的配置,Spring MVC会在处理请求时使用增强后的处理器适配器,能够在请求处理前后执行额外的逻辑。这展示了如何利用动态代理来增强Spring组件的功能。

静态代理在编译时确定并生成代理类,适合较简单的情况。动态代理在运行时生成代理类,灵活性更高,适合需要在运行时动态添加行为的情况。在Spring MVC中,通过HandlerAdapter和动态代理,可以实现强大的请求处理逻辑定制。