微服务安全与权限-shiro[03 权限与总结]

200 阅读4分钟

这是我参与8月更文挑战的第10天,活动详情查看:8月更文挑战

使用权限

用户登录之后,就会读取用户的角色权限名并将其放入权限存储中。当调用被标注需要权限的方法时,就会从权限存储对象中读取是否有该权限,进而执行

数据库表: user表:id,account,password,name,role_id role表:id,name,enname

修改Realm类,用于身份验证和获取角色权限

public class Realm extends AuthorizingRealm {
​
   @Resource
   UserService userService;
​
   @Override
   /*权限配置*/
   protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollectionprincipalCollection) {
       System.out.println("授权=====");
//       获取当前登录的用户
       Subject subject = SecurityUtils.getSubject();
//         这个user即在密码认证中闯入的user,所以它携带着角色权限信息
       User user = (User) subject.getPrincipal();
//           权限存储对象,用于存储查询出的用户的角色及权限(如果有的话)
           SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
           info.addRole(user.getRoleName());
           return info;
​
  }
​
   @Override
   /*身份验证*/
   protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationTokenauthenticationToken) throws AuthenticationException {
       System.out.println("认证===");
//       获取输入的帐号密码
       UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
       User user = userService.selectAccount(token.getUsername());
       if(user==null){
           return null;
      }
//       shiro后台验证密码是否正确
       return new SimpleAuthenticationInfo(user,user.getPassword(),getName());
  }
}

修改配置文件,增加扫描shiro配置类注解

开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证

配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能

   @Bean
   public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
       return new LifecycleBeanPostProcessor();
  }
​
   @Bean
   @DependsOn({ "lifecycleBeanPostProcessor" })
   public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
       DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = newDefaultAdvisorAutoProxyCreator();
       advisorAutoProxyCreator.setProxyTargetClass(true);
       return advisorAutoProxyCreator;
  }
​
   @Bean
   public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
       AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = newAuthorizationAttributeSourceAdvisor();
       authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager());
       return authorizationAttributeSourceAdvisor;
  }

在需要使用权限验证的地方增加权限要求@RequiresRoles 以controller层为例

@RequiresRoles("check")
   @RequestMapping({"/getImages"})
   public Map<String,ObjectgetImages(){
       HashMapStringObject > map = new HashMap<>();
//       获取待审核的图片列表
       List<Image> imageList = imageService.selectByFlag(staticConstant.UNCHECK);
//       获取所有信息放入list集合
       ListMapStringObject > > maps = forEachImageList(imageList);
       map.put("list", maps);
       map.put("message","返回待审核图片路径集合");
       return map;
  }

退出登录 在路径拦截map中配置

map.put("/login/logout","logout");

无需在controller中自己写,访问该路径即可退出登录

如果需要使用swagger,需要在拦截路径中放行swagger资源

      map.put("/doc.html","anon");
      map.put("/swagger-ui.html", "anon");
      map.put("/swagger-resources/**", "anon");

自定义Token验证

一般来说,shiro生成返回token后,浏览器会自动在cookie中添加token值用于身份验证。当vue无法手动插入cookie时,可以使用自定义的session验证来通过,无序从cookie中读取。

获取sessionId,原本是根据sessionKey来获取一个sessionId 重写的部分多了一个把获取到的token设置到request的部分。这是因为app调用登陆接口的时候,是没有token的,登陆成功后,产生了token,我们把它放到request中,返回结果给客户端的时候,把它从request中取出来,并且传递给客户端,客户端每次带着这个token过来,就相当于是浏览器的cookie的作用,也就能维护会话了

1.创建自定义sessionManager

public class shiroSession extends DefaultWebSessionManager {
​
   private  static final String TOKEN = "token";
​
   private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
​
       public  shiroSession(){
           super();
      }
   protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
       //获取请求头中的 AUTH_TOKEN 的值,如果请求头中有 AUTH_TOKEN 则其值为sessionId。shiro就是通过sessionId 来控制的
       String sessionId = WebUtils.toHttp(request).getHeader(TOKEN);
​
       if (StringUtils.isEmpty(sessionId)){
           //如果没有携带id参数则按照父类的方式在cookie进行获取sessionId
           return super.getSessionId(request, response);
​
      } else {
           request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
           request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
           request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
           return sessionId;
      }
  }
​
}
​

2.在shiro配置文件中应用自定义的sessionManager

@Bean
   public SessionManager sessionManager(){
//       实现我们写的shiroSession
       shiroSession shiroSession = new shiroSession();
       shiroSession.setSessionDAO(new EnterpriseCacheSessionDAO());
       return shiroSession;
  }
​
​
   @Bean
   public DefaultWebSecurityManager defaultWebSecurityManager(){
       DefaultWebSecurityManager defaultWebSecurityManager = newDefaultWebSecurityManager();
       defaultWebSecurityManager.setRealm(realm());
//       将自定义的session管理放入
       defaultWebSecurityManager.setSessionManager(sessionManager());
       return defaultWebSecurityManager;
  }

3.测试 登录获取token 20201117142813

总结

shiro的具体执行流程:

1.通过controller请求时,会触发realm的认证方法AuthenticationInfo(通过token连接)

具体操作:realm的AuthenticationInfo用于验证数据是否正确读取数据库数据

如果通过认证,此时数据库的user就会变成subject

2.realm的授权方法AuthorizationInfo会对当前用户Subject进行授权

具体操作:realmd的AuthorizationInfo会从读取的数据为当前用户授权(addStringPermission)

将数据库的user的权限赋给subject

3.DefaultWebSecurityManager关联realmShiroFilterFactoryBean

具体操作:setRealm(realm) :manager获取realm的数据

4.ShiroFilterFactoryBean设置资源权限,设置登录和未授权跳转页面

具体操作:1.setSecurityManager(defaultWebSecurityManager); 通过manager获取realm的数据

2.map.put("/toadd","perms[user:add]"); 设置资源的权限,并将map放入过滤器

  1. setXXXurl("") 设置跳转页面

将资源设置和获取的数据进行对比,成功就访问,不成功跳转未授权页面

5.controller根据shiro返回对应的信息