springsecurity-OAuth2.0:第8章:基于web与方法授权 目录一 授权1.1 授权api逻辑说明1.2 授权方法二 案例实战2.1 初始化数据库2.2 查询数据库权限2.2.1 dao层2.2.2 service层2.3 第一种方式web授权2.3.1 在配置文件中设置​编辑 2.3.2 测试2.4 第一种方式web授权查询数据库2.4.1 核心思想2.4.2 核心代码-自定义元数据源加载 URL 权限规则2.4.3 核心代码-自定义权限决策器校验用户是否有权限2.4.4 核心代码-Security 配置类绑定自定义组件2.4.5 核心代码-登录时加载用户权限UserDetailsService2.5 第二种方式方法授权2.5.1 配置EnableGlobalMethodSecurity2.5.2 然后向方法在类或接口上添加注解就会限制对该方法的访问2.5.3 测试2.6 方法注解的分析一 授权1.1 授权api逻辑说明授权的方式包括web授权和方法授权web授权是通过 url拦截进行授权方法授权是通过 方法拦截进行授权。1.他们都会调用accessDecisionManager进行授权决策2.若为web授权则拦截器为FilterSecurityInterceptor3.若为方 法授权则拦截器为MethodSecurityInterceptor。3.如果同时通过web授权和方法授权则先执行web授权再执行方 法授权最后决策通过则允许访问资源否则将禁止访问。1.2 授权方法访问授权的方法二 案例实战2.1 初始化数据库1.角色表CREATE TABLE t_role ( id varchar(32) NOT NULL, role_name varchar(255) DEFAULT NULL, description varchar(255) DEFAULT NULL, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, status char(1) NOT NULL, PRIMARY KEY (id), UNIQUE KEY unique_role_name (role_name) ) ENGINEInnoDB DEFAULT CHARSETutf8insert into t_role(id,role_name,description,create_time,update_time,status) values (1,管理员,NULL,NULL,NULL,);2.角色关联表CREATE TABLE t_user_role ( user_id varchar(32) NOT NULL, role_id varchar(32) NOT NULL, create_time datetime DEFAULT NULL, creator varchar(255) DEFAULT NULL, PRIMARY KEY (user_id,role_id) ) ENGINEInnoDB DEFAULT CHARSETutf8insert into t_user_role(user_id,role_id,create_time,creator) values (1,1,NULL,NULL);3.权限表CREATE TABLE t_permission ( id varchar(32) NOT NULL, code varchar(32) NOT NULL COMMENT 权限标识符, description varchar(64) DEFAULT NULL COMMENT 描述, url varchar(128) DEFAULT NULL COMMENT 请求地址, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8insert into t_permission(id,code,description,url) values (1,p1,测试资源 1,/user/r1),(2,p3,测试资源2,/user/r2);4.角色权限表CREATE TABLE t_role_permission ( role_id varchar(32) NOT NULL, permission_id varchar(32) NOT NULL, PRIMARY KEY (role_id,permission_id) ) ENGINEInnoDB DEFAULT CHARSETutf8insert into t_role_permission(role_id,permission_id) values (1,1),(1,2);2.2 查询数据库权限2.2.1 dao层!-- 查询用户信息 -- select idfindPermissionsByUserId resultTypecom.ljf.spt.security.model.PermissionDto SELECT id,code,description,url FROM t_permission WHERE id IN ( SELECT permission_id FROM t_role_permission WHERE role_id IN ( SELECT role_id FROM t_user_role WHERE user_id #{userId} ) ) /select2.2.2 service层package com.ljf.spt.security.service; import com.ljf.spt.security.dao.UserMapper; import com.ljf.spt.security.model.PermissionDto; import com.ljf.spt.security.model.UserDto; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; /** * ClassName: SpringDataUserDetailsService * Description: TODO * Author: liujianfu * Date: 2021/08/14 09:44:20 * Version: V1.0 **/ Service public class SpringDataUserDetailsService implements UserDetailsService { Autowired private UserMapper userMapper; Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //将来连接数据库根据账号查询用户信息 UserDto userDto userMapper.getUserByUsername(username); if(userDto null){ //如果用户查不到返回null由provider来抛出异常 return null; } //权限 // String [] authoritys{p1}; //根据用户的id查询用户的权限 ListPermissionDto permissionsList userMapper.findPermissionsByUserId(userDto.getId()); ListString permissions new ArrayList(); permissionsList.forEach(c - permissions.add(c.getCode())); //将permissions转成数组 String[] permissionArray new String[permissions.size()]; permissions.toArray(permissionArray); UserDetails userDetails User.withUsername(userDto.getUsername()).password(userDto.getPassword()).authorities(permissionArray).build(); return userDetails; } }2.3 第一种方式web授权2.3.1 在配置文件中设置通过给http.authorizeRequests()添加多个子节点来定制需求到我们的URL,进行灵活的授权控制:1.http.authorizeRequests()方法有多个子节点每个macher按照他们的声明顺序执行。2指定/user/r1URL拥有p1权限能够访问3指定/user/r2URL拥有p2权限能够访问4.指定了除了r1、r2、之外/user/**资源同时通过身份认证就能够访问这里使用SpELSpring Expression Language表达式。。6剩余的尚未匹配的资源不做保护。这里需要注意规则的顺序是重要的,更具体的规则应该先写也就是说 权限范围小的写在最上面范围大的写在下面。.antMatchers(/admin/**).hasRole(ADMIN).antMatchers(/admin/login).permitAll()因为/ admin / login已经被/ admin / **规则匹配,因此第二个规则被忽略。应该改为.antMatchers(/admin/login).permitAll().antMatchers(/admin/**).hasRole(ADMIN)2.3.2 测试1.登录2.访问资源13.访问资源2可以看到无权限访问bejing用户的角色为管理员只拥有权限为p1p1的路径为/user/r12.4 第一种方式web授权查询数据库2.4.1 核心思想查询出指定用户的权限列表判断页面发出请求的权限请求路径是否在该用户的权限列表中。2.4.2 核心代码-自定义元数据源加载 URL 权限规则FilterInvocationSecurityMetadataSource作用请求过来根据当前 URL从数据库查出该接口需要哪些权限Component public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { Autowired private PermissionService permissionService; // key: url, value: 该接口需要的权限集合 private MapString, CollectionConfigAttribute urlPermMap new HashMap(); // 每次请求都会执行获取当前接口需要什么权限 Override public CollectionConfigAttribute getAttributes(Object object) throws IllegalArgumentException { FilterInvocation fi (FilterInvocation) object; String requestUrl fi.getRequestUrl(); // 1. 先刷新数据库最新权限也可加缓存定时刷新 loadUrlPermData(); // 2. 匹配urlant匹配和antMatchers效果一致 for (Map.EntryString, CollectionConfigAttribute entry : urlPermMap.entrySet()) { AntPathMatcher matcher new AntPathMatcher(); if (matcher.match(entry.getKey(), requestUrl)) { return entry.getValue(); } } // 无配置的接口默认放行 return SecurityConfig.createList(ROLE_ANONYMOUS); } // 从数据库加载所有url和权限 private void loadUrlPermData() { ListSysPermission permList permissionService.getAllPerms(); MapString, CollectionConfigAttribute tempMap new HashMap(); for (SysPermission perm : permList) { String url perm.getUrl(); String permsTag perm.getPerms(); // ConfigAttribute 包装权限标识 tempMap.put(url, SecurityConfig.createList(permsTag)); } urlPermMap tempMap; } Override public CollectionConfigAttribute getAllConfigAttributes() { return null; } Override public boolean supports(Class? clazz) { return FilterInvocation.class.isAssignableFrom(clazz); } }2.4.3 核心代码-自定义权限决策器校验用户是否有权限AccessDecisionManager拿到接口要求的权限对比当前登录用户的权限Component public class CustomAccessDecisionManager implements AccessDecisionManager { Override public void decide(Authentication authentication, Object object, CollectionConfigAttribute configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { // 1. 当前登录用户拥有的所有权限 Collection? extends GrantedAuthority userAuths authentication.getAuthorities(); SetString userPermSet userAuths.stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toSet()); // 2. 接口需要的权限 for (ConfigAttribute attr : configAttributes) { String needPerm attr.getAttribute(); // 用户包含该权限则放行 if (userPermSet.contains(needPerm)) { return; } } // 无匹配权限抛出无权限异常 throw new AccessDeniedException(无访问权限); } Override public boolean supports(ConfigAttribute attribute) { return true; } Override public boolean supports(Class? clazz) { return FilterInvocation.class.isAssignableFrom(clazz); } }2.4.4 核心代码-Security 配置类绑定自定义组件不再写.antMatchers(/user/r1).hasAuthority(p1)全部动态读取数据库Configuration EnableWebSecurity public class SecurityConfig { Autowired private CustomFilterInvocationSecurityMetadataSource metadataSource; Autowired private CustomAccessDecisionManager accessDecisionManager; Autowired private UserDetailsService userDetailsService; Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeRequests() .withObjectPostProcessor(new ObjectPostProcessorFilterSecurityInterceptor() { Override public O extends FilterSecurityInterceptor O postProcess(O fsi) { // 注入自定义元数据源 fsi.setSecurityMetadataSource(metadataSource); // 注入自定义决策管理器 fsi.setAccessDecisionManager(accessDecisionManager); return fsi; } }) .anyRequest().authenticated(); // 登录、跨域、csrf、异常处理省略... return http.build(); } // 密码加密 Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } }2.4.5 核心代码-登录时加载用户权限UserDetailsService登录认证时从数据库查询当前用户所有权限标识封装到GrantedAuthorityService public class CustomUserDetailsService implements UserDetailsService { Autowired private UserService userService; Autowired private PermissionService permissionService; Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 1. 查询用户 SysUser user userService.getByUsername(username); if (user null) { throw new UsernameNotFoundException(用户不存在); } // 2. 查询该用户所有权限 p1 p2 SetString perms permissionService.getUserPermsByUserId(user.getId()); // 3. 封装权限 ListGrantedAuthority authorities perms.stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); // 封装用户信息返回 return new User(user.getUsername(), user.getPassword(), authorities); } }豆包 - 字节跳动旗下 AI 智能助手2.5 第二种方式方法授权从Spring Security2.0版 它支持服务层方法的安全性的支持。方法注权限校验有PreAuthorize,PostAuthorize, Secured三类注解。2.5.1 配置EnableGlobalMethodSecurity我们可以在任何Configuration实例上使用EnableGlobalMethodSecurity注释来启用基于注解的安全性。EnableGlobalMethodSecurity(securedEnabled true,prePostEnabled true)2.5.2然后向方法在类或接口上添加注解就会限制对该方法的访问这里在controller的方法设置权限2.5.3 测试访问资源1访问资源22.6 方法注解的分析使用如下代码可启用prePost注解的支持