日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

SpringSecurity - 用户动态授权 及 动态角色权限

發布時間:2024/3/12 javascript 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 SpringSecurity - 用户动态授权 及 动态角色权限 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、SpringSecurity 動態授權

上篇文章我們介紹了SpringSecurity的動態認證,上篇文章就說了SpringSecurity 的兩大主要功能就是認證和授權,既然認證以及學習了,那本篇文章一起學習了SpringSecurity 的動態授權。

上篇文章地址:https://blog.csdn.net/qq_43692950/article/details/122393435

二、SpringSecurity 授權

我們接著上篇文章的項目繼續修改,上篇文章中有說到我們WebSecurityConfig配制類中的configure(HttpSecurity http)這個方法就是用來做授權的,現在就可以來體驗一下了,比如我們修改以admin為開頭的接口,權限或角色中需要有admin:

@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/admin/**").hasAuthority("admin").antMatchers("/**").fullyAuthenticated().and().formLogin().permitAll().and().csrf().disable();}

下面使用admin用戶訪問admin/test接口:

報了403無權限的錯誤,因為我們設置了admin/**接口必須要有admin這個權限,可以看下上篇文章中寫的UserService類:

這邊直接給用戶設定死了一個admin角色,這里就有個問題了權限和角色有什么區別,其實在SpringSecurity 中權限和角色都放在了一起,可以說概念上是一樣的,但角色是以ROLE_開頭的。

其中還需注意的是如果授權角色可以使用hasRole()和hasAnyRole(),如果是授權權限則使用hasAuthority() 和 hasAnyAuthority()

角色授權:授權代碼需要加ROLE_前綴,controller上使用時不要加前綴。
權限授權:設置和使用時,名稱保持一至即可。

所以可以修改UserService類:

在此請求接口:

現在就有權限訪問了,但是寫死肯定不是我們要的效果,所以此時可以將角色放在數據庫中,通過查詢數據庫動態獲取用戶的角色。

下面就需要在數據庫中創建role角色表:

CREATE TABLE `role` (`id` int(11) NOT NULL AUTO_INCREMENT,`role` varchar(255) NOT NULL,`role_describe` varchar(255) NOT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

角色肯定是和人有關系的,而且有時多對多的關系,所以根據關系模型我們要抽取出一個角色用戶關系表:

CREATE TABLE `user_role` (`id` int(11) NOT NULL AUTO_INCREMENT,`userid` int(11) NOT NULL,`roleid` int(11) NOT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;


對于角色的新增和關聯用戶,無非就是數據庫的增刪改,這里不做演示了,直接在創建好表可以在表中添加幾條角色,并關聯用戶:


添加RoleEntity實體

@Data @TableName("/role") public class RoleEntity {private Long id;private String role;@TableField("role_describe")private String roleDescribe; }

RoleMapper類,并寫根據用戶id查詢全部角色的接口:

@Mapper @Repository public interface RoleMapper extends BaseMapper<RoleEntity> {@Select("SELECT r.id,r.role,r.role_describe FROM user_role u,role r where u.roleid = r.id AND u.userid = #{userId}")List<RoleEntity> getAllRoleByUserId(@Param("userId") Integer userId); }

修改UserService類:

@Service public class UserService implements UserDetailsService {@AutowiredUserMapper userMapper;@AutowiredRoleMapper roleMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {LambdaQueryWrapper<UserEntity> wrapper = new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getUsername, username);UserEntity userEntity = userMapper.selectOne(wrapper);if (userEntity == null) {throw new UsernameNotFoundException("用戶不存在!");}List<GrantedAuthority> auths = roleMapper.getAllRoleByUserId(userEntity.getId()).stream().map(r -> new SimpleGrantedAuthority(r.getRole())).collect(Collectors.toList());userEntity.setRoles(auths);return userEntity;}public boolean register(String userName, String password) {UserEntity entity = new UserEntity();entity.setUsername(userName);entity.setPassword(new BCryptPasswordEncoder().encode(password));entity.setEnabled(true);entity.setLocked(false);return userMapper.insert(entity) > 0;} }

下面就可以測試了,在瀏覽器再次訪問上面的接口:

但是發現是403,原因是我們給admin設置的是權限admin,不是角色,數據庫中存的是ROLE_admin,這里是想讓大家對兩者的區別更加深刻下,修改數據庫為admin

重新啟動再次訪問:

已經可以訪問了。上面大家應該對權限和角色有了一定的了解,下面對授權和授予角色的方法做下說明:

  • hasRole
    如果用戶具備給定角色就允許訪問,否則出現 403。給接口授權時無需寫ROLE_開頭,因為底層代碼會自動添加與之進行匹配,用戶添加角色時必須寫ROLE_。

  • hasAnyRole
    表示用戶具備任何一個條件都可以訪問。

  • hasAuthority
    如果當前的主體具有指定的權限,則返回 true,否則返回 false

  • hasAnyAuthority
    如果當前的主體有任何提供的角色(給定的作為一個逗號分隔的字符串列表)的話,返true

現在我們已經了解怎么樣給用戶授權了,也知道怎么給接口賦予權限了,但是還是有個問題:

這個都在代碼里面寫死也不合適呀,其實這里有兩種方案,一種是地址和角色的固定變化不大的場景下,可以在這里從數據庫中讀取出來通過HttpSecurity對象映射角色,但這種方案不太好在項目運行期間動態添加角色。還有一種方案就是實現FilterInvocationSecurityMetadataSource接口,在這里面根據當前訪問的url返回該url所具有的全部角色。顯然后者更為靈活,但每次訪問一次接口都取獲取全部的角色肯定性能有所損失。

下面分別實現下這兩種情況:

三、數據庫讀取通過HttpSecurity授權

上面已經創建了role角色表,現在要做url和role的關聯,所以添加一個menu表用來存放url:

CREATE TABLE `menu` (`id` int(11) NOT NULL AUTO_INCREMENT,`pattern` varchar(255) NOT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

menu和role也都是多對多的關系,所以也需要建一個menu_role關系表:

CREATE TABLE `menu_role` (`id` int(11) NOT NULL AUTO_INCREMENT,`menu_id` int(11) NOT NULL,`role_id` int(11) NOT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

還是在表中添加一些數據:


創建MeunEntity實體類:

@Data @TableName("menu") public class MeunEntity {@TableId(type = IdType.AUTO)private Integer id;private String pattern; }

MeunMapper 繼承BaseMapper

@Mapper @Repository public interface MeunMapper extends BaseMapper<MeunEntity> { }

修改RoleMapper:

@Mapper @Repository public interface RoleMapper extends BaseMapper<RoleEntity> {@Select("SELECT r.id,r.role,r.role_describe FROM user_role u,role r where u.roleid = r.id AND u.userid = #{userId}")List<RoleEntity> getAllRoleByUserId(@Param("userId") Integer userId);@Select("SELECT r.id,r.role,r.role_describe FROM menu_role m,role r where m.role_id = r.id AND m.menu_id = #{menuId}")List<RoleEntity> getAllRoleByMenuId(@Param("menuId") Integer menuId); }

修改WebSecurityConfig:

@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserService userService;@AutowiredMeunMapper meunMapper;@AutowiredRoleMapper roleMapper;@Overridepublic void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService).passwordEncoder(password());}@BeanPasswordEncoder password() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry authorizeRequests = http.authorizeRequests();List<MeunEntity> meunEntities = meunMapper.selectList(null);meunEntities.forEach(m -> {authorizeRequests.antMatchers(m.getPattern()).hasAnyAuthority(roleMapper.getAllRoleByMenuId(m.getId()).stream().map(RoleEntity::getRole).toArray(String[]::new));});authorizeRequests.antMatchers("/**").fullyAuthenticated().and().formLogin().permitAll().and().csrf().disable();}@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/register/**");} }

重啟項目,然后再次訪問測試接口,已經實現和上面相同的效果:

四、通過FilterInvocationSecurityMetadataSource 動態角色

上面已經實現了第一種方案,下面繼續實現第二中方案,下面創建一個類實現FilterInvocationSecurityMetadataSource 接口:

@Component public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {@AutowiredMeunMapper meunMapper;@AutowiredRoleMapper roleMapper;//用來實現ant風格的Url匹配AntPathMatcher antPathMatcher = new AntPathMatcher();@Overridepublic Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {//獲取當前請求的UrlString requestUrl = ((FilterInvocation) object).getRequestUrl();List<MeunEntity> list = meunMapper.selectList(null);List<ConfigAttribute> roles = new ArrayList<>();list.forEach(m -> {if (antPathMatcher.match(m.getPattern(), requestUrl)) {List<ConfigAttribute> allRoleByMenuId = roleMapper.getAllRoleByMenuId(m.getId()).stream().map(r -> new SecurityConfig(r.getRole())).collect(Collectors.toList());roles.addAll(allRoleByMenuId);}});if (!roles.isEmpty()) {return roles;}return SecurityConfig.createList("ROLE_LOGIN");}@Overridepublic Collection<ConfigAttribute> getAllConfigAttributes() {return null;}@Overridepublic boolean supports(Class<?> clazz) {return true;} }

還需創建一個CustomAccessDecisionManager用來實現AccessDecisionManager:

@Component public class CustomAccessDecisionManager implements AccessDecisionManager {@Overridepublic void decide(Authentication auth, Object object, Collection<ConfigAttribute> ca) throws AccessDeniedException, InsufficientAuthenticationException {for (ConfigAttribute configAttribute : ca) {//如果請求Url需要的角色是ROLE_LOGIN,說明當前的Url用戶登錄后即可訪問if ("ROLE_LOGIN".equals(configAttribute.getAttribute()) && auth instanceof UsernamePasswordAuthenticationToken){ return;}Collection<? extends GrantedAuthority> auths = auth.getAuthorities(); //獲取登錄用戶具有的角色for (GrantedAuthority grantedAuthority : auths) {if (configAttribute.getAttribute().equals(grantedAuthority.getAuthority())){return;}}}throw new AccessDeniedException("權限不足");}@Overridepublic boolean supports(ConfigAttribute configAttribute) {return true;}@Overridepublic boolean supports(Class<?> aClass) {return true;} }

修改WebSecurityConfig

@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserService userService;@AutowiredCustomAccessDecisionManager customAccessDecisionManager;@AutowiredCustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;@Overridepublic void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService).passwordEncoder(password());}@BeanPasswordEncoder password() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {@Overridepublic <O extends FilterSecurityInterceptor> O postProcess(O o) {o.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);o.setAccessDecisionManager(customAccessDecisionManager);return o;}}).antMatchers("/**").fullyAuthenticated().and().formLogin().permitAll().and().csrf().disable();}@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/register/**");}@BeanRoleHierarchy roleHierarchy() {RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();String hierarchy = "ROLE_admin > ROLE_user > ROLE_common";roleHierarchy.setHierarchy(hierarchy);return roleHierarchy;} }

再次測試上面的測試接口,可以發現也達到了相同的效果:

但是此時是動態角色的,我們可以創建一個新用戶,給新用戶一個新的角色,再給該角色賦予admin/**的權限。

創建用戶adc

添加角色:

角色綁定用戶:

角色綁定menu:


下面清楚瀏覽器的緩存,使用abc用戶登錄:


成功訪問接口,說明動態角色權限已經生效了。


喜歡的小伙伴可以關注我的個人微信公眾號,獲取更多學習資料!

總結

以上是生活随笔為你收集整理的SpringSecurity - 用户动态授权 及 动态角色权限的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。