默認情況下, Spring Security 並不啟用方法級的安全管控. 啟用方法級的管控后, 可以針對不同的方法通過注解設置不同的訪問條件.
Spring Security 支持三種方法級注解, 分別是 JSR-205 注解/@Secured 注解/prePostEnabled注解. 這些注解不僅可以直接加 controller 方法上, 也可以注解 Service 或 DAO 類中的方法.
啟用方法級的管控代碼是, 新建一個 WebSecurityConfigurerAdapter Configuration 類, 加上 @EnableGlobalMethodSecurity() 注解, 通過@EnableGlobalMethodSecurity 參數開啟相應的方法級的管控.
===================================
JSR-205 注解
===================================
通過 @EnableGlobalMethodSecurity(jsr250Enabled=true), 開啟 JSR-205 注解.
@DenyAll 注解, 拒絕所有的訪問
@PermitAll 注解, 運行所有訪問
@RolesAllowed({"USER","ADMIN"}), 該方法只允許有 ROLE_USER 或 ROLE_ADMIN 角色的用戶訪問.
===================================
@Secured 注解
===================================
通過 @EnableGlobalMethodSecurity(securedEnabled=true), 開啟 @Secured 注解.
只有滿足角色的用戶才能訪問被注解的方法, 否則將會拋出 AccessDenied 異常.
例子:
@Secured("ROLE_TELLER","ROLE_ADMIN"), 該方法只允許 ROLE_TELLER 或 ROLE_ADMIN 角色的用戶訪問.
@Secured("IS_AUTHENTICATED_ANONYMOUSLY"), 該方法允許匿名用戶訪問.
===================================
@PreAuthorize 類型的注解(支持 Spring 表達式)
===================================
@EnableGlobalMethodSecurity(prePostEnabled=true), 開啟 prePostEnabled 相關的注解.
JSR-205 和 @Secured 注解功能較弱, 不支持 Spring EL 表達式. 推薦使用 @PreAuthorize 類型的注解.
具體有4個注解.
@PreAuthorize 注解, 在方法調用之前, 基於表達式結果來限制方法的使用.
@PostAuthorize 注解, 允許方法調用, 但是如果表達式結果為 false, 將拋出一個安全性異常.
@PostFilter 注解, 允許方法調用, 但必要按照表達式來過濾方法的結果.
@PreFilter 注解, 允許方法調用, 但必須在進入方法之前過來輸入值.
例子:
@PreAuthorize("hasRole('ADMIN')") //必須有 ROLE_ADMIN 角色
public void addBook(Book book);
//必須同時具備 ROLE_ADMIN 和 ROLE_DBA 角色
@PreAuthorize("hasRole('ADMIN') AND hasRole('DBA')")
public void addBook(Book book);
@PreAuthorize ("#book.owner == authentication.name")
public void deleteBook(Book book);
@PostAuthorize ("returnObject.owner == authentication.name")
public Book getBook();
===================================
@PreAuthorize 表達式
===================================
1. returnObject 保留名
對於 @PostAuthorize 和 @PostFilter 注解, 可以在表達式中使用 returnObject 保留名, returnObject 代表着被注解方法的返回值, 我們可以使用 returnObject 保留名對注解方法的結果進行驗證.
比如:
@PostAuthorize ("returnObject.owner == authentication.name")
public Book getBook();
2. 表達式中的 # 號
在表達式中, 可以使用 #argument123 的形式來代表注解方法中的參數 argument123.
比如:
@PreAuthorize ("#book.owner == authentication.name")
public void deleteBook(Book book);
還有一種 #argument123 的寫法, 即使用 Spring Security @P注解來為方法參數起別名, 然后在 @PreAuthorize 等注解表達式中使用該別名. 不推薦這種寫法, 代碼可讀性較差.
@PreAuthorize("#c.name == authentication.name")
public void doSomething(@P("c") Contact contact);
3. 內置表達式有:
| 表達式 | 備注 |
| hasRole([role]) | 如果有當前角色, 則返回 true(會自動加上 ROLE_ 前綴) |
| hasAnyRole([role1, role2]) | 如果有任一角色即可通過校驗, 返回true,(會自動加上 ROLE_ 前綴) |
| hasAuthority([authority]) | 如果有指定權限, 則返回 true |
| hasAnyAuthority([authority1, authority2]) | 如果有任一指定權限, 則返回true |
| principal | 獲取當前用戶的 principal 主體對象 |
| authentication | 獲取當前用戶的 authentication 對象, |
| permitAll | 總是返回 true, 表示全部允許 |
| denyAll | 總是返回 false, 代表全部拒絕 |
| isAnonymous() | 如果是匿名訪問, 返回true |
| isRememberMe() | 如果是remember-me 自動認證, 則返回 true |
| isAuthenticated() | 如果不是匿名訪問, 則返回true |
| isFullAuthenticated() | 如果不是匿名訪問或remember-me認證登陸, 則返回true |
| hasPermission(Object target, Object permission) | |
| hasPermission(Object target, String targetType, Object permission) | |
===================================
示例代碼
===================================
----------------------------
pom.xml
----------------------------
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
----------------------------
SecurityConfig 配置類
----------------------------
SecurityConfig 配置類開啟方法級管控(僅啟用 prePostEnabled 類的注解), 並 hard coded 了一個內置的用戶清單.
因為沒有重載 configure(HttpSecurity http) 方法, 用的是Spring security 的 basic Authentication 和內置的login form.
@EnableWebSecurity
@Configuation @EnableGlobalMethodSecurity(prePostEnabled=true) public class SecurityConfig extends WebSecurityConfigurerAdapter{ @Override public void configure(AuthenticationManagerBuilder builder) throws Exception { builder.inMemoryAuthentication() .withUser("123").password("123").roles("USER") .and() .withUser("ADMIN").password("ADMIN").roles("ADMIN") .and() .withUser("124").password("124").roles("USER2"); } @SuppressWarnings("deprecation") @Bean public NoOpPasswordEncoder passwordEncoder() { return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance(); } }
----------------------------
BookService 配置類
----------------------------
為BookService Service類的方法加上 @PreAuthorize 注解, 對權限進行控制.
@Service class BookService{ @PreAuthorize("hasRole('ADMIN')") public void addBook(Book book) { System.out.println("you have added a book successfully"); } @PreAuthorize("hasAnyRole('ADMIN','USER')") public Book getBook() { Book book=new Book("A"); return book ; } @PreAuthorize("hasRole('ADMIN')") public void deleteBook(Book book) { System.out.println("Book deleted"); } }
----------------------------
Entity 和 Controller 類
----------------------------
Entity 和 Controller 類沒有特別之處, 這里不用太關注.
@RestController("/")
class BookController{
@Autowired
private BookService bookService ;
@GetMapping("/get")
public String getBook() {
Book book =bookService.getBook() ;
return book.getName() ;
}
}
class Book{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Book(String name) {
this.name=name ;
}
}
----------------------------
測試結果
----------------------------
使用 123 用戶(角色是 USER )登陸, 可以訪問 http://localhost:8080/get ; 使用 124 用戶(角色是 USER2 )登陸, 訪問 http://localhost:8080/get 報下面的403權限問題. 符合預期

===================================
參考
===================================
https://www.concretepage.com/spring/spring-security/preauthorize-postauthorize-in-spring-security
https://www.jianshu.com/p/bcb3c6445b2b
https://www.jianshu.com/p/41b7c3fb00e0
https://blog.csdn.net/l18767118724/article/details/72934564
https://spring.io/guides/topicals/spring-security-architecture/
https://www.logicbig.com/tutorials/spring-framework/spring-security/roles-allowed-annotation.html
