最近在考虑增强系统的安全性,主要使用 spring boot security & spring boot oauth2 来保证系统的安全。

主要依赖(Dependence)

1
2
3
4
5
6
7
8
9
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency<!-- Spring Security OAuth2 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

全局依赖配置(GlobalAuthenticationConfigurerAdapter)

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
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableGlobalAuthentication
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class GlobalSecurityConfig extends GlobalAuthenticationConfigurerAdapter {

@Autowired
private UserDetailsService userDetailsService;

@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}

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

用户控制器(UserController)

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
package cn.edu.cqjtu.oj.web.v1;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.annotation.JsonView;

import cn.edu.cqjtu.oj.domain.TbUser;
import cn.edu.cqjtu.oj.domain.TbUser.UserSimpleView;
import cn.edu.cqjtu.oj.service.UserService;
import cn.edu.cqjtu.oj.support.OAuth2User;
import cn.edu.cqjtu.oj.support.UserQueryCondition;
import io.swagger.annotations.ApiOperation;

/**
* 用户控制器
*
* @author johnniang
*
*/
@RestController
@RequestMapping("/api/v1/users")
public class UserController {

private Logger logger = LoggerFactory.getLogger(getClass());

@Autowired
private UserService userService;

// @Autowired
// private MyValidator validator;

@GetMapping
@ApiOperation("分页查询用户")
@JsonView(UserSimpleView.class)
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public List<TbUser> list(UserQueryCondition condition) {
// 查询符合条件的用户
List<TbUser> users = userService.query(condition);
return users;
}

@GetMapping("/me")
@ApiOperation("查询用户详情")
@JsonView(UserSimpleView.class)
public OAuth2User getDetails(Authentication authentication) {
if (logger.isDebugEnabled()) {
logger.debug("Principal class: {}", authentication.getClass());
}
return (OAuth2User) authentication.getPrincipal();
}

}

遇到的问题(Problem)

在 UserController 中一旦加入了@PreAuthorize("hasAuthority('ROLE_ADMIN')")注解,将会导致 Spring 扫描不到 UserController,最终没办法访问此 URL,所以将会出现 404 错误。

解决方案(Solution)

搞了两天了,已经无计可施了。各种 Google / Baidu,没有任何一个解决方案适合这个问题。

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
package cn.edu.cqjtu.oj.web;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import cn.edu.cqjtu.oj.web.v1.UserController;

@RestController
@RequestMapping("/test")
public class TestController {

Logger logger = LoggerFactory.getLogger(getClass());

@Autowired
private UserController userController;

@GetMapping
public void testUserController() {
if (logger.isDebugEnabled()) {
logger.debug("User Controller: {}", userController);
}
}
}

最后自己写了一个 TestController,就直接用使用 Logger 打印了一下 userController 这个对象。问题终于浮出水面:

1
2
3
4
5
6
7
8
9
10
11
12
***************************
APPLICATION FAILED TO START
***************************

Description:

The bean 'userController' could not be injected as a 'cn.edu.cqjtu.oj.web.v1.UserController' because it is a JDK dynamic proxy that implements:


Action:

Consider injecting the bean as one of its interfaces or forcing the use of CGLib-based proxies by setting proxyTargetClass=true on @EnableAsync and/or @EnableCaching.

这不就是使用了 JDK 自身的代理么,导致没办法使用 AOP 进行监控方法的执行权限。

最后一根救命稻草终于结束了这么久这么长久的搜寻工作。

1
2
3
spring:
aop:
proxy-target-class: true