第五章 細粒度權限控制
5.1 前置細節【Role和Authority的區別】
5.1.1 用戶擁有的權限表示
- roles("ADMIN","學徒","宗師")
- authorities("USER","MANAGER");
5.1.2 給資源授予權限(角色或權限)
//.antMatchers("/level1/**").hasRole("學徒") //.antMatchers("/level1/**").hasAnyRole("學徒","ADMIN")//擁有任何一個角色都可以訪問 .antMatchers("/level1/**").hasAnyAuthority("學徒","ADMIN") //擁有任何一個權限都可以訪問 .antMatchers("/level2/**").hasRole("大師") .antMatchers("/level3/**").hasRole("宗師") |
5.1.3 權限:【roles和authorities區別】
- roles("ADMIN","學徒","宗師")
增加"ROLE_"前綴存放:【"ROLE_ADMIN","ROLE_學徒","ROLE_宗師"】
表示擁有的權限。一個角色表示的是多個權限
用戶傳入的角色不能以ROLE_開頭,否則會報錯。ROLE_是自動加上的
如果我們保存的用戶的角色:直接傳入角色的名字,權限【new SimpleGrantedAuthority("ROLE_" + role)】保存即可
- authorities("USER","MANAGER");
原樣存放:【"USER","MANAGER"】
表示擁有的權限。
如果我們保存的是真正的權限;直接傳入權限名字,權限【new SimpleGrantedAuthority(role)】保存
無論是Role還是Authority都保存在 List<GrantedAuthority>,每個用戶都擁有自己的權限集合->List<GrantedAuthority>
5.1.4 驗證用戶權限
- 通過角色(權限)驗證:
.antMatchers("/level1/**").hasRole("學徒")
.antMatchers("/level1/**").hasAnyRole("學徒","ADMIN")
擁有任何一個角色都可以訪問
驗證時會自動增加"ROLE_"進行查找驗證:【"ROLE_學徒","ROLE_ADMIN"】
通過權限驗證
.antMatchers("/level1/**").hasAuthority("學徒")
.antMatchers("/level1/**").hasAnyAuthority("學徒","ADMIN")
擁有任何一個權限都可以訪問
驗證時原樣查找進行驗證:【"學徒","ADMIN"】
5.2 細粒度的資源控制
- authenticated():通過認證的用戶都可以訪問
- permitAll():允許所有人訪問,即使未登錄
- authorizeRequests():更細粒度的控制
- access(String):
//SpEL:Spring表達式
.access("hasRole('大師') AND hasAuthority('user:delete') OR hasIpAddress('192.168.50.15')")
5.3 細粒度的資源控制相應註解
使用註解與SpEl進行細粒度權限控制
5.3.1 開啓註解控制權限模式
@EnableWebSecurity:開啓 Spring Security 註解
@EnableGlobalMethodSecurity(prePostEnabled=true):開啓全局的細粒度方法級別權限控制功能
5.3.2 幾個權限檢查註解
1 @PreAuthorize:方法執行前檢查
@PreAuthorize("hasRole('ADMIN')") public void addUser(User user){ //如果具有ROLE_ADMIN 權限 則訪問該方法 .... } |
2 @PostAuthorize:方法執行後檢查,失敗拋異常
@PostAuthorize:允許方法調用,但是,如果表達式結果爲false拋出異常 //returnObject可以獲取返回對象user,判斷user屬性username是否和訪問該方法的用戶對象的用戶名一樣。不一樣則拋出異常。 @PostAuthorize("returnObject.user.username==principal.username") public User getUser(int userId){ //允許進入 ... return user; } |
3 @PostFilter:允許方法調用,但是按照表達式過濾方法結果
//將結果過濾,即選出性別爲男的用戶 @PostFilter("returnObject.user.sex=='男' ") public List<User> getUserList(){ //允許進入 ... return user; } |
4 @PreFilter:允許方法調用,但必須在進入方法前過濾輸入值
5 @Secured:擁有指定角色纔可以訪問方法
@Secured('ADMIN') 等價於 @PreAuthorize("hasRole('ADMIN')") |
5.4 細粒度的資源控制註解中可寫的表達式
所有能使用的表達式見上面文檔連接
5.5 細粒度權限控制實現步驟 ★
5.5.1 開啓全局的細粒度方法級別權限控制功能
@EnableGlobalMethodSecurity(prePostEnabled = true) @EnableWebSecurity @Configuration public class AppSecurityConfig extends WebSecurityConfigurerAdapter { } |
5.5.2 將手動授權的方式註釋掉
//.antMatchers("/level1/**").hasRole("學徒") //.antMatchers("/level1/**").hasAnyRole("學徒","ADMIN") //.antMatchers("/level1/**").hasAnyAuthority("學徒","ADMIN") //.antMatchers("/level1/**").hasAuthority("學徒") //.antMatchers("/level2/**").hasRole("大師") //.antMatchers("/level3/**").hasRole("宗師") |
5.5.3 給訪問資源的方法增加註解,進行訪問授權
@Controller public class GongfuController { /** * 授權(權限檢查)使用AOP;MethodSecurityInterceptor *
*/ @PreAuthorize("hasRole('學徒') AND hasAuthority('luohan')") @GetMapping("/level1/1") public String leve1Page(){ return "/level1/1"; } @PreAuthorize("hasRole('學徒') AND hasAuthority('wudang')") @GetMapping("/level1/2") public String leve1Page2(){ return "/level1/2"; } @PreAuthorize("hasRole('學徒') AND hasAuthority('quanzhen')") @GetMapping("/level1/3") public String leve1Page3(){ return "/level1/3"; } } |
5.5.4 通過數據庫加載用戶權限
@Service public class AppUserDetailsServiceImpl implements UserDetailsService {
@Autowired JdbcTemplate jdbcTemplate ;
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { String sql = "select * from t_admin where loginacct=?"; Map<String, Object> map = jdbcTemplate.queryForMap(sql, username);
//查詢用戶擁有的角色集合 String sql1="SELECT t_role.* FROM t_role LEFT JOIN t_admin_role ON t_admin_role.roleid=t_role.id WHERE t_admin_role.adminid=?"; List<Map<String, Object>> roleList = jdbcTemplate.query(sql1, new ColumnMapRowMapper(), map.get("id"));
//查詢用戶擁有的權限集合 String sql2 = "SELECT distinct t_permission.* FROM t_permission LEFT JOIN t_role_permission ON t_role_permission.permissionid = t_permission.id LEFT JOIN t_admin_role ON t_admin_role.roleid=t_role_permission.roleid WHERE t_admin_role.adminid=?"; List<Map<String, Object>> permissionList = jdbcTemplate.query(sql2, new ColumnMapRowMapper(), map.get("id"));
//用戶權限=【角色+權限】 Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for (Map<String, Object> rolemap : roleList) { String rolename = rolemap.get("name").toString(); authorities.add(new SimpleGrantedAuthority("ROLE_"+rolename)); } for (Map<String, Object> permissionmap : permissionList) { String permissionName = permissionmap.get("name").toString(); if(!StringUtils.isEmpty(permissionName)) { authorities.add(new SimpleGrantedAuthority(permissionName)); } }
//return new User(map.get("loginacct").toString(),map.get("userpswd").toString(), //AuthorityUtils.createAuthorityList("ADMIN","USER")); return new User(map.get("loginacct").toString(),map.get("userpswd").toString(),authorities); } } |
5.5.5 準備數據
查詢用戶擁有的角色集合 |
查詢用戶擁有的權限集合 |
SELECT t_role.* FROM t_role LEFT JOIN t_admin_role ON t_admin_role.roleid = t_role.id WHERE t_admin_role.userid = 1 |
SELECT DISTINCT t_permission.* FROM t_permission LEFT JOIN t_role_permission ON t_role_permission.permissionid = t_permission.id LEFT JOIN t_admin_role ON t_admin_role.roleid = t_role_permission.roleid WHERE t_admin_role.userid = 1 |
5.5.6 測試結果
登錄認證通過,可以登錄到成功頁面
訪問【學徒】角色下的資源:
/level1/1 羅漢拳不可以訪問
/level1/2 武當長拳可以訪問
/level1/3 全真劍法不可以訪問