javascript
SpringSecurity之授权
1.權限管理
1.1.認證
身份認證,就是判斷一個用戶是否為合法用戶的處理過程。Spring Security中支持多種不同方式的認證,但是無論開發者使用哪種方式認證,都不會影響授權功能使用。因為Spring Security很好做到了認證和授權解耦。
1.2.授權
授權,即訪問控制,控制誰能訪問哪些資源。簡單的理解授權就是根據系統提前設置好的規則,給用戶分配可以訪問某一個資源的權限,用戶根據自己所具有的權限,去執行相應操作。
2.授權核心概念
認證成功之后會將當前登錄用戶信息保存到Authentication對象中,Authentication對象中有一個getAuthorities()方法,用來返回當前登錄用戶具備的權限信息,也就是當前用戶具有權限信息。該方法的返回值為Collection<? extends GrantedAuthority>,當需要進行權限判斷時,就會根據集合返回權限信息調用相應 方法進行判斷。
那么問題來了,針對于這個返回值GrantedAuthority應該如何理解?是角色還是權限?
基于角色進行權限管理
基于資源進行權限管理 -->權限字符串
R(Role Resources) B(base) A(access) C(controll)
我們針對于授權可以是基于角色權限管理和基于資源權限管理,從設計層面上來說,角色和權限是兩個完全不同的東西:權限是一些具體操作,角色則是某些權限集合。如:READ_BOOK和ROLE_ADMIN是完全不同的。因此至于返回值是什么取決于你的業務設計情況:
-
基于角色權限設計就是:用戶<>角色<>資源 三者關系 返回就是用戶的角色
-
基于資源權限設計就是: 用戶<>權限<>資源 三者關系 返回就是用戶的權限
-
基于角色和資源權限設計就是: 用戶<>角色<>權限<==>資源 返回統稱為用戶的權限
為什么統稱為權限,因為從代碼層面角色和權限沒有太大不同都是權限,特別是在Spring Security中。角色和權限處理方式基本上都是一樣的。唯一區別SpringSecurity在很多時候會自動給角色添加一個**ROLE_**前綴,而權限則不會自動添加。
3.權限管理策略
可以訪問系統中哪些資源(http url method)
Spring Security中提供的權限管理策略主要有兩種類型:
- 基于過濾器(URL)的權限管理(FilterSecurityInterceptor)
- 基于過濾器的權限管理主要用來攔截HTTP請求,攔截下來之后,根據HTTP請求地址進行權限校驗。
- 基于AOP的權限管理(MethodSecurityInterceptor)
- 基于AOP權限管理主要是用來處理方法級別的權限問題。當需要調用某一個方法時,通過AOP將操作攔截下來,然后判斷用戶是否具備相關的權限。
3.1.基于URL的權限管理
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter {//創建內存數據源public UserDetailsService userDetailsService(){InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();inMemoryUserDetailsManager.createUser(User.withUsername("root").password("{noop}123").roles("ADMIN","USER").build());inMemoryUserDetailsManager.createUser(User.withUsername("lisi").password("{noop}123").roles("USER").build());inMemoryUserDetailsManager.createUser(User.withUsername("win7").password("{noop}123").authorities("READ_INFO").build());return inMemoryUserDetailsManager;}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService());}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeHttpRequests().mvcMatchers("/admin").hasRole("ADMIN") //具有 admin角色.mvcMatchers("/user").hasRole("USER") //具有user角色.mvcMatchers("/getInfo").hasAuthority("READ_INFO") //具有read_info權限.antMatchers(HttpMethod.GET,"admin").hasRole("ADMIN") // .regexMatchers().hasRole() //好處: 支持正則表達式.anyRequest().authenticated().and().formLogin().and().csrf().disable();} }權限表達式
3.2.基于方法的權限管理
基于方法的權限管理主要是通過AOP來實現的,Spring Security中通過MethodSecurityInterceptor來提供相關的實現。不同在于FilterSecurityInterceptor只是在請求之前進行前置處理,MethodSecurityInterceptor除了前置處理之外還可以進行后置處理。前置處理就是在請求之前判斷是否具備相應的權限,后置處理則是對方法的執行結果進行二次過濾。前置處理和后置處理分別對應了不同的實現類。
@EnableGlobalMethodSecurity
EnableGlobalMethodSecurity該注解是用來開啟權限注解,用法如下:
@Configuration @EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true,jsr250Enabled=true) public class SecurityConfig extends WebsecurityConfigurerAdapter{}- perPostEnabled:開啟Spring Security提供的四個權限注解,@PostAuthorize、@PostFilter、@PreAuthorize以及@PreFilter。
- securedEnabled:開啟Spring Security提供的@Secured注解支持,該注解不支持權限表達式
- jsr250Enabled:開啟JSR-250提供的注解,主要是@DenyAll、@PermitAll、@RolesAll同樣這些注解也不支持權限表達式
這些機遇方法的權限管理相關的注解,一般來說只要設置prePostEnabled=true就夠用了。
@RestController @RequestMapping("/hello") public class AuthorizeMethodController {@PreAuthorize("hasRole('ADMIN') and authentication.name=='root'")@GetMappingpublic String hello(){return "hello";}@PreAuthorize("authentication.name ==#name") //參數與認證的用戶名一致才可以訪問@GetMapping("/name")public String hello(String name){return "hello:" + name;}@PreFilter(value ="filterObject.id%2!=0",filterTarget = "users") //filterTarget表示要過濾的參數,必須是數組、集合類型 filterObject是數組中的對象,如User。@PostMapping("/users")public void addUsers(@RequestBody List<User> users){System.out.println("users = "+ users);}@PostAuthorize("returnObject.id ==1") //表示方法返回前處理@GetMapping("/userId")public User getUserById(Integer id){return new User(id,"zkt");}@PostFilter("filterObject.id%2==0") //用來對方法返回值進行過濾@GetMapping("/lists")public List<User> getAll(){List<User> users = new ArrayList<>();for(int i = 0; i < 10; i++){users.add(new User(i,"zkt"+i));}return users;}@Secured({"ROLE_USER"}) //只能判斷角色@GetMapping("/secured")public User getUserByUsername(){return new User(99,"secured");}@Secured({"ROLE_ADMIN","ROLE_USER"}) //具有其中一個即可@GetMapping("/username")public User getUserByUsername2(String username){return new User(99,username);}@PermitAll@GetMapping("/permitAll")public String permitAll(){return "PermitAll";}@DenyAll@GetMapping("/denyAll")public String denyAll(){return "DenyAll";}@RolesAllowed({"ROLE_ADMIN","ROLE_USER"}) //具有其中一個角色即可@GetMapping("/rolesAllowed")public String rolesAllowed(){return "RolesAllowed";}}4.原理分析
- ConfigAttribute 在Sring Security中,用戶請求一個資源(通常是一個接口或者一個Java 方法)需要的角色會被封裝成一個ConfigAttribute對象,在ConfigAttribute中只有一個getAttribute方法,該方法返回一個String字符串,就是角色的名稱。一般來說,角色名稱都帶有一個ROLE_ 前綴,投票器AccessDecisionVoter所做的事情,其實就是比較用戶所具有的各個角色和請求某個資源所需的ConfigAttribue之間的關系。
- AccessDecisionVoter和AccessDecisionManager都有眾多的實現類,在AccessDecisionManager中會逐個遍歷AccessDecisionVoter,進而決定是否允許用戶訪問,因而AccessDecisionVoter和AccessDecisionManager兩者的關系類似于AuthenticationProvider和ProviderManager的關系。
5.動態授權實戰
在前面的案例中,我們配置的URL攔截規則和請求URL所需要的權限都是通過代碼來配置的,這樣就比較死板,如果想要調整訪問某一個URL所需要的權限,就需要修改代碼。
動態管理權限規則就是我們將URL攔截規則和訪問URI所需要的權限都保存在數據庫中,這樣,在不修改源代碼的情況下,只需要修改數據庫中的數據,就可以對權限進行調整。
5.1.庫表設計
用戶 <----> 中間表 <----> 角色 <----> 中間表 <---->菜單
set Names utf8mb4; set foreign_key_checks = 0; drop table if exists `menu`; create table `menu`(`id` int(11) not null auto_increment,`pattern` varchar(128) default null,primary key(`id`))engine=Innodb auto_increment=4 default charset=utf8;begin; insert into `menu` values(1,'/admin/**'); insert into `menu` values(2,'/user/**'); insert into `menu` values(3,'/guest/**'); commit;drop table if exists `menu_role`; create table `menu_role`(`id` int(11) not null auto_increment,`mid` int(11) default null,`rid` int(11) default null,primary key(`id`),key `mid`(`mid`),key `rid`(`rid`),constraint `menu_role_ibfk_1` foreign key(`mid`) REFERENCES `menu`(`id`),constraint `menu_role_ibfk_2` foreign key(`rid`) REFERENCES `role`(`id`) )engine=Innodb auto_increment=5 default charset=utf8;begin; insert into `menu_role` values(1,1,1); insert into `menu_role` values(2,2,2); insert into `menu_role` values(3,3,3); insert into `menu_role` values(4,3,2);drop table if exists `role`;create table `role`(`id` int(11) not null auto_increment,`name` varchar(32) default null,`nameZh` varchar(32) default null,primary key(`id`) )engine=innodb auto_increment=4 default charset=utf8;begin; insert into `role` values(1,'ROLE_ADMIN','系統管理員'); insert into `role` values(2,'ROLE_USER','普通用戶'); insert into `role` values(3,'ROLE_GUEST','游客'); commit;drop table if exists `user`; create table `user`(`id` int(11) not null auto_increment,`username` varchar(32) default null,`password` varchar(255) default null,`enabled` tinyint(1) default null,`locked` tinyint(1) default null,primary key(`id`))engine=InnoDB auto_increment=4 default charset=utf8;begin;insert into `user` values(1,'admin','{noop}123',1,0);insert into `user` values(2,'user','{noop}123',1,0);insert into `user` values(3,'zkt','{noop}123',1,0);commit;drop table if exists `user_role`;create table `user_role`(`id` int(11) not null auto_increment,`uid` int(11) default null,`rid` int(11) default null,primary key(`id`),key `uid`(`uid`),key `rid`(`rid`),CONSTRAINT `user_role_ibfk_1` foreign key(`uid`) REFERENCES `user`(id),constraint `user_role_ibfk_2` foreign key(`rid`) REFERENCES `role`(`id`) )engine=innodb auto_increment=5 default charset=utf8;begin;insert into `user_role` VALUES(1,1,1);insert into `user_role` VALUES(2,1,2);insert into `user_role` VALUES(3,2,2);insert into `user_role` VALUES(4,3,3);commit;set foreign_key_checks =1;5.2.springboot依賴
<!--druid--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.8</version></dependency><!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.38</version></dependency><!--mybatis-springboot--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.0</version></dependency>5.3.springboot配置設置
# 應用名稱 spring.application.name=spring-security-17dynamic-authorize # 應用服務 WEB 訪問端口 server.port=8080spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/security_authorize?characterEncoding=UTF-8 spring.datasource.username=root spring.datasource.password=root mybatis.mapper-locations=classpath:com/zkt/mapper/*.xml mybatis.type-aliases-package=com.zkt.entity5.4.實體類
public class Role {private Integer id;private String name;private String nameZh;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getNameZh() {return nameZh;}public void setNameZh(String nameZh) {this.nameZh = nameZh;} } public class User implements UserDetails {private Integer id;private String username;private String password;private boolean enabled;private boolean locked;private List<Role> roles;@Overridepublic String getPassword() {return password;}@Overridepublic String getUsername() {return username;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return !locked;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return enabled;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return roles.stream().map(r ->new SimpleGrantedAuthority(r.getName())).collect(Collectors.toList());}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public void setUsername(String username) {this.username = username;}public void setPassword(String password) {this.password = password;}public void setEnabled(boolean enabled) {this.enabled = enabled;}public boolean isLocked() {return locked;}public void setLocked(boolean locked) {this.locked = locked;}public List<Role> getRoles() {return roles;}public void setRoles(List<Role> roles) {this.roles = roles;} } public class Menu {private Integer id;private String pattern;private List<Role> roles;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getPattern() {return pattern;}public void setPattern(String pattern) {this.pattern = pattern;}public List<Role> getRoles() {return roles;}public void setRoles(List<Role> roles) {this.roles = roles;} }5.5.mapper文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zkt.dao.UserMapper"><!--根據用戶名查詢用戶方法--><!--查詢單個--><select id="loadUserByUsername" resultType="com.zkt.entity.User">select *from userwhere username = #{username}</select><!-- 根據用戶id 查詢角色信息--><select id="getUserRoleByUid" resultType="com.zkt.entity.Role">select r.* from role r, user_role urwhere r.id = ur.ridand ur.rid = #{uid}</select> </mapper> <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zkt.dao.MenuMapper"><resultMap id="MenuResultMap" type="com.zkt.entity.Menu"><id property="id" column="id"/><result property="pattern" column="pattern"></result><collection property="roles" ofType="com.zkt.entity.Role"><id column="rid" property="id"/><result column="rname" property="name"/><result column="rnameZh" property="nameZh"/></collection></resultMap><select id="getAllMenu" resultMap="MenuResultMap">select m.*,r.id as rid,r.name as rname,r.nameZh as rnameZhfrom menu mleft join menu_role mr on m.`id` = mr.`mid`left join role r on r.`id` =mr.`rid`</select> </mapper>5.6.Dao層
@Mapper public interface MenuMapper {List<Menu> getAllMenu(); } @Mapper public interface UserMapper {//根據用戶id獲取角色信息List<Role> getUserRoleByUid(Integer uid);//根據用戶名獲取用戶信息User loadUserByUsername(String username); }5.7.Service層
@Service public class UserService implements UserDetailsService {private UserMapper userMapper;@Autowiredpublic UserService(UserMapper userMapper) {this.userMapper = userMapper;}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//1.根據用戶名查詢信息User user = userMapper.loadUserByUsername(username);if( user == null){throw new UsernameNotFoundException("用戶不存在");}user.setRoles(userMapper.getUserRoleByUid(user.getId()));return user;} } @Service public class MenuService {private final MenuMapper menuMapper;@Autowiredpublic MenuService(MenuMapper menuMapper) {this.menuMapper = menuMapper;}public List<Menu> getAllMenu(){return menuMapper.getAllMenu();} }5.8.Controller層
@RestController public class HelloController {@GetMapping("/admin/hello")public String admin(){return "hello admin";}@GetMapping("/user/hello")public String user(){return "hello user";}@GetMapping("/guest/hello")public String guest(){return "hello guest";}@GetMapping("/hello")public String hello(){return "hello";} }5.9.自定義CustomSecurityMetaSource(相關)
@Component public class CustomSecurityMetaSource implements FilterInvocationSecurityMetadataSource {private MenuService menuService;@Autowiredpublic CustomSecurityMetaSource(MenuService menuService) {this.menuService = menuService;}AntPathMatcher antPathMatcher = new AntPathMatcher();/** 自定義動態資源權限元數據信息* */@Overridepublic Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {//1.當前請求對象String requestURI = ((FilterInvocation)object).getRequest().getRequestURI();//2.查詢所有菜單List<Menu> allMenu = menuService.getAllMenu();for(Menu menu : allMenu){if(antPathMatcher.match(menu.getPattern(),requestURI)){String[] roles = menu.getRoles().stream().map(r ->r.getName()).toArray(String[]::new);return SecurityConfig.createList(roles);}}return null;}@Overridepublic Collection<ConfigAttribute> getAllConfigAttributes() {return null;}@Overridepublic boolean supports(Class<?> clazz) {return FilterInvocation.class.isAssignableFrom(clazz);} }5.10.自定義配置類中配置(相關)
@Configuration @EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true,jsr250Enabled=true) public class MySecurityConfig extends WebSecurityConfigurerAdapter {private final CustomSecurityMetaSource customSecurityMetadataSource;private final UserDetailsService userDetailsService;@Autowiredpublic MySecurityConfig(CustomSecurityMetaSource customSecurityMetadataSource,UserDetailsService userDetailsService) {this.customSecurityMetadataSource = customSecurityMetadataSource;this.userDetailsService = userDetailsService;}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService);}@Overrideprotected void configure(HttpSecurity http) throws Exception {//1.獲取工廠對象ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class);//2.設置自定義url權限處理http.apply(new UrlAuthorizationConfigurer<>(applicationContext)).withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {@Overridepublic <O extends FilterSecurityInterceptor> O postProcess(O object) {object.setSecurityMetadataSource(customSecurityMetadataSource);//是否拒絕公共資源訪問object.setRejectPublicInvocations(false);return object;}});http.formLogin().and().csrf().disable();} }5.11.測試
數據庫表中對應的權限如下
使用不同的用戶進行登錄進行訪問相對應的資源。
總結
以上是生活随笔為你收集整理的SpringSecurity之授权的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SuperMap iObjects C+
- 下一篇: python之路金角大王_Python