经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
SpringBoot整合SpringSecurityOauth2实现鉴权动态权限问题
来源:jb51  时间:2022/6/20 16:23:07  对本文有异议

写在前面

思考:为什么需要鉴权呢?

系统开发好上线后,API接口会暴露在互联网上会存在一定的安全风险,例如:爬虫、恶意访问等。因此,我们需要对非开放API接口进行用户鉴权,鉴权通过之后再允许调用。

准备

spring-boot:2.1.4.RELEASE

spring-security-oauth2:2.3.3.RELEASE(如果要使用源码,不要随意改动这个版本号,因为2.4往上的写法不一样了)

mysql:5.7

效果展示

这边只用了postman做测试,暂时未使用前端页面来对接,下个版本角色菜单权限分配的会有页面的展示

1、访问开放接口http://localhost:7000/open/hello 

2、不带token访问受保护接口http://localhost:7000/admin/user/info

3、登录后获取token,带上token访问,成功返回了当前的登录用户信息

实现

oauth2一共有四种模式,这边就不做讲解了,网上搜一搜,千篇一律

因为现在只考虑做单方应用的,所以使用的是密码模式。

后面会出一篇SpringCloud+Oauth2的文章,网关鉴权

讲一下几个点吧

1、拦截器配置动态权限

新建一个 MySecurityFilter类,继承AbstractSecurityInterceptor,并实现Filter接口

初始化,自定义访问决策管理器

  1. @PostConstruct
  2. public void init(){
  3. super.setAuthenticationManager(authenticationManager);
  4. super.setAccessDecisionManager(myAccessDecisionManager);
  5. }

自定义 过滤器调用安全元数据源

  1. @Override
  2. public SecurityMetadataSource obtainSecurityMetadataSource() {
  3. return this.mySecurityMetadataSource;
  4. }

先来看一下自定义过滤器调用安全元数据源的核心代码

以下代码是用来获取到当前请求进来所需要的权限(角色)

  1. /**
  2. * 获得当前请求所需要的角色
  3. * @param object
  4. * @return
  5. * @throws IllegalArgumentException
  6. */
  7. @Override
  8. public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
  9. String requestUrl = ((FilterInvocation) object).getRequestUrl();
  10.  
  11. if (IS_CHANGE_SECURITY) {
  12. loadResourceDefine();
  13. }
  14. if (requestUrl.indexOf("?") > -1) {
  15. requestUrl = requestUrl.substring(0, requestUrl.indexOf("?"));
  16. }
  17. UrlPathMatcher matcher = new UrlPathMatcher();
  18. List<Object> list = new ArrayList<>(); //无需权限的,直接返回
  19. list.add("/oauth/**");
  20. list.add("/open/**");
  21. if(matcher.pathsMatchesUrl(list,requestUrl))
  22. return null;
  23.  
  24. Set<String> roleNames = new HashSet();
  25. for (Resc resc: resources) {
  26. String rescUrl = resc.getResc_url();
  27. if (matcher.pathMatchesUrl(rescUrl, requestUrl)) {
  28. if(resc.getParent_resc_id() != null && resc.getParent_resc_id().intValue() == 1){ //默认权限的则只要登录了,无需权限匹配都可访问
  29. roleNames = new HashSet();
  30. break;
  31. }
  32. Map map = new HashMap();
  33. map.put("resc_id", resc.getResc_id());
  34. // 获取能访问该资源的所有权限(角色)
  35. List<RoleRescDTO> roles = roleRescMapper.findAll(map);
  36. for (RoleRescDTO rr : roles)
  37. roleNames.add(rr.getRole_name());
  38. }
  39. }
  40.  
  41. Set<ConfigAttribute> configAttributes = new HashSet();
  42. for(String roleName:roleNames)
  43. configAttributes.add(new SecurityConfig(roleName));
  44.  
  45. log.debug("【所需的权限(角色)】:" + configAttributes);
  46.  
  47. return configAttributes;
  48. }

再来看一下自定义访问决策管理器核心代码,这段代码主要是判断当前登录用户(当前登录用户所拥有的角色会在最后一项写到)是否拥有该权限角色

  1. @Override
  2. public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
  3. if(configAttributes == null){ //属于白名单的,不需要权限
  4. return;
  5. }
  6. Iterator<ConfigAttribute> iterator = configAttributes.iterator();
  7. while (iterator.hasNext()){
  8. ConfigAttribute configAttribute = iterator.next();
  9. String needPermission = configAttribute.getAttribute();
  10. for (GrantedAuthority ga: authentication.getAuthorities()) {
  11. if(needPermission.equals(ga.getAuthority())){ //有权限,可访问
  12. return;
  13. }
  14. }
  15. }
  16. throw new AccessDeniedException("没有权限访问");
  17.  
  18. }

2、自定义鉴权异常返回通用结果

为什么需要这个呢,如果不配置这个,对于前端,后端来说都很难去理解鉴权失败返回的内容,还不能统一解读,废话不多说,先看看不配置和配置了的返回情况

(1)未自定义前,没有携带token去访问受保护的API接口时,返回的结果是这样的

(2)我们规定一下,鉴权失败的接口返回接口之后,变成下面这种了,是不是更利于我们处理和提示用户

好了,来看一下是在哪里去配置的吧

我们资源服务器OautyResourceConfig,重写下下面这部分的代码,来自定义鉴权异常返回的结果

大伙可以参考下这个https://www.jb51.net/article/131668.htm

  1. @Override
  2. public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
  3. resources.authenticationEntryPoint(authenticationEntryPoint) //token失效或没携带token时
  4. .accessDeniedHandler(requestAccessDeniedHandler); //权限不足时
  5. }

3、获取当前登录用户

第一种:使用JWT携带用户信息,拿到token后再解析

暂不做解释

第二种:写一个SecurityUser实现UserDetails接口(这个工程中使用的是这一种)

原来的只有UserDetails接口只有username和password,这里我们加上我们系统中的User

  1. protected User user;
  2. public SecurityUser(User user) {
  3. this.user = user;
  4. }
  5.  
  6. public User getUser() {
  7. return user;
  8. }

在BaseController,每个Controller都会继承这个的,在里面写给getUser()的方法,只要用户带了token来访问,我们可以直接获取当前登录用户的信息了

  1. protected User getUser() {
  2. try {
  3. SecurityUser userDetails = (SecurityUser) SecurityContextHolder.getContext().getAuthentication()
  4. .getPrincipal();
  5.  
  6. User user = userDetails.getUser();
  7. log.debug("【用户:】:" + user);
  8.  
  9. return user;
  10. } catch (Exception e) {
  11. }
  12. return null;
  13. }

那么用户登录成功后,如何去拿到用户的角色集合等呢,这里面就要实现UserDetailsService接口了

  1. @Service
  2. public class TokenUserDetailsService implements UserDetailsService{
  3.  
  4. @Autowired
  5. private LoginService loginService;
  6.  
  7. @Override
  8. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
  9. User user = loginService.loadUserByUsername(username); //这个我们拎出来处理
  10. if(Objects.isNull(user))
  11. throw new UsernameNotFoundException("用户名不存在");
  12. return new SecurityUser(user);
  13. }
  14. }

然后在我们的安全配置类中设置UserDetailsService为上面的我们自己写的就行

  1. @Override
  2. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  3. auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
  4. }

最后我们只需要在loginService里面实现我们的方法就好,根据我们的实际业务处理判断该用户是否存在等

  1. @Override
  2. public User loadUserByUsername(String username){
  3. log.debug(username);
  4. Map map = new HashMap();
  5. map.put("username",username);
  6. map.put("is_deleted",-1);
  7. User user = userMapper.findByUsername(map);
  8. if(user != null){
  9. map = new HashMap();
  10. map.put("user_id",user.getUser_id());
  11. //查询用户的角色
  12. List<UserRoleDTO> userRoles = userRoleMapper.findAll(map);
  13. user.setRoles(listRoles(userRoles));
  14. //权限集合
  15. Collection<? extends GrantedAuthority> authorities = merge(userRoles);
  16. user.setAuthorities(authorities);
  17. return user;
  18. }
  19. return null;
  20.  
  21. }

大功告成啦,赶紧动起手来吧!

附上源码地址:https://gitee.com/jae_1995/spring-boot-oauth2

数据库文件在这

到此这篇关于SpringBoot整合SpringSecurityOauth2实现鉴权-动态权限的文章就介绍到这了,更多相关SpringBoot整合SpringSecurityOauth2内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号