专栏首页cwl_JavaSaaS-HRM中的认证授权

SaaS-HRM中的认证授权

3 SaaS-HRM中的认证授权

3.1 需求分析

实现基于Shiro的SaaS平台的统一权限管理。我们的SaaS-HRM系统是基于微服务构建,所以在使用Shiro鉴权的时候,就需要将认证信息保存到统一的redis服务器中完成。这样,每个微服务都可以通过指定cookie中的sessionid获取公共的认证信息。

3.2 搭建环境

3.2.1 导入依赖

父工程导入Shiro的依赖

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.3.2</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.3.2</version>
</dependency>
<dependency>
    <groupId>org.crazycake</groupId>
    <artifactId>shiro-redis</artifactId>
    <version>3.0.0</version>
</dependency>

3.2.2 配置值对象

不需要存入redis太多的用户数据,和获取用户信息的返回对象一致即可,需要实现AuthCachePrincipali接口

@Setter
@Getter
public class ProfileResult implements Serializable,AuthCachePrincipal {
    private String mobile;
    private String username;
    private String company;
    private String companyId;
    private Map<String,Object> roles = new HashMap<>();
 //省略
}

3.2.3 配置未认证controller

为了在多个微服务中使用,配置公共的未认证未授权的Controller

@RestController
@CrossOrigin
public class ErrorController {
    //公共错误跳转
    @RequestMapping(value="autherror")
    public Result autherror(int code) {
        return code ==1?new Result(ResultCode.UNAUTHENTICATED):new
Result(ResultCode.UNAUTHORISE);
   }
}

3.2.4 自定义realm授权

common模块下创建公共的认证与授权realm,需要注意的是,此realm只处理授权数据即可,认证方法需要在登录模块中补全。

public class IhrmRealm extends AuthorizingRealm {
    @Override
    public void setName(String name) {
        super.setName("ihrmRealm");
   }
    //授权方法
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection
principalCollection) {
        //1.获取安全数据
        ProfileResult result = (ProfileResult)principalCollection.getPrimaryPrincipal();
        //2.获取权限信息
        Set<String> apisPerms = (Set<String>)result.getRoles().get("apis");
        //3.构造权限数据,返回值
        SimpleAuthorizationInfo info = new  SimpleAuthorizationInfo();
        info.setStringPermissions(apisPerms);
        return info;
   }
    /**
     * 认证方法
     */
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken
authenticationToken) throws AuthenticationException {
        return null;
   }
}

3.3.5 自定义会话管理器

之前的程序使用jwt的方式进行用户认证,前端发送后端的是请求头中的token。为了适配之前的程序,在shiro中需要更改sessionId的获取方式。很好解决,在shiro的会话管理中,可以轻松的使用请求头中的内容作为sessionid

public class IhrmWebSessionManager extends DefaultWebSessionManager {
    private static final String AUTHORIZATION = "Authorization";
    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
    public IhrmWebSessionManager(){
        super();
   }
    protected Serializable getSessionId(ServletRequest request, ServletResponse
response){
        String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        if(StringUtils.isEmpty(id)){
            //如果没有携带id参数则按照父类的方式在cookie进行获取
            return super.getSessionId(request, response);
       }else{
            id = id.replace("Bearer ", "");
            //如果请求头中有 authToken 则其值为sessionId
           
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,REFERENCED_S
ESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID,id);
           
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID,Boolean.TR
UE);
            return id;
       }
   }
}

3.3 用户认证

3.3.1 配置用户登录

 //用户名密码登录
    @RequestMapping(value="/login",method = RequestMethod.POST)
    public Result login(@RequestBody Map<String,String> loginMap) {
        String mobile = loginMap.get("mobile");
        String password = loginMap.get("password");
        try {
            //1.构造登录令牌 UsernamePasswordToken
            //加密密码
            password = new Md5Hash(password,mobile,3).toString();  //1.密码,盐,加密次数
            UsernamePasswordToken upToken = new UsernamePasswordToken(mobile,password);
            //2.获取subject
            Subject subject = SecurityUtils.getSubject();
            //3.调用login方法,进入realm完成认证
             subject.login(upToken);
            //4.获取sessionId
            String sessionId = (String)subject.getSession().getId();
            //5.构造返回结果
            return new Result(ResultCode.SUCCESS,sessionId);
       }catch (Exception e) {
            return new Result(ResultCode.MOBILEORPASSWORDERROR);
       }
   }

3.3.2 shiro认证

配置用户登录认证的realm域,只需要继承公共的IhrmRealm补充其中的认证方法即可

public class UserIhrmRealm extends IhrmRealm {
    @Override
    public void setName(String name) {
        super.setName("customRealm");
   }
    @Autowired
    private UserService userService;
    
    //认证方法
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken
authenticationToken) throws AuthenticationException {
        //1.获取用户的手机号和密码
        UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;
        String mobile = upToken.getUsername();
        String password = new String( upToken.getPassword());
        //2.根据手机号查询用户
        User user = userService.findByMobile(mobile);
        //3.判断用户是否存在,用户密码是否和输入密码一致
        if(user != null && user.getPassword().equals(password)) {
            //4.构造安全数据并返回(安全数据:用户基本数据,权限信息 profileResult)
            ProfileResult result = null;
            if("user".equals(user.getLevel())) {
                result = new ProfileResult(user);
           }else {
                Map map = new HashMap();
                if("coAdmin".equals(user.getLevel())) {
                    map.put("enVisible","1");
               }
                List<Permission> list = permissionService.findAll(map);
                result = new ProfileResult(user,list);
           }
            //构造方法:安全数据,密码,realm域名
            SimpleAuthenticationInfo info = new
SimpleAuthenticationInfo(result,user.getPassword(),this.getName());
            return info;
       }
        //返回null,会抛出异常,标识用户名和密码不匹配
         return null;
   }
}

3.3.3 获取session数据

baseController中使用shiro从redis中获取认证数据

	 //使用shiro获取
    @ModelAttribute
    public void setResAnReq(HttpServletRequest request,HttpServletResponse response) {
        this.request = request;
        this.response = response;
        //获取session中的安全数据
        Subject subject = SecurityUtils.getSubject();
        //1.subject获取所有的安全数据集合
        PrincipalCollection principals = subject.getPrincipals();
        if(principals != null && !principals.isEmpty()){
            //2.获取安全数据
            ProfileResult result = (ProfileResult)principals.getPrimaryPrincipal();
            this.companyId = result.getCompanyId();
            this.companyName = result.getCompany();
       }
   }

3.4 用户授权

在需要使用的接口上配置@RequiresPermissions(“API-USER-DELETE”)

3.5 配置

构造shiro的配置类

@Configuration
public class ShiroConfiguration {
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    //配置自定义的Realm
    @Bean
    public IhrmRealm getRealm() {
        return new UserIhrmRealm();
   }
    //配置安全管理器
     @Bean
    public SecurityManager securityManager() {
        //使用默认的安全管理器
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 自定义session管理 使用redis
        securityManager.setSessionManager(sessionManager());
        // 自定义缓存实现 使用redis
        securityManager.setCacheManager(cacheManager());
        //将自定义的realm交给安全管理器统一调度管理
        securityManager.setRealm(getRealm());
        return securityManager;
   }
    //Filter工厂,设置对应的过滤条件和跳转条件
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        //1.创建shiro过滤器工厂
        ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
        //2.设置安全管理器
        filterFactory.setSecurityManager(securityManager);
        //3.通用配置(配置登录页面,登录成功页面,验证未成功页面)
        filterFactory.setLoginUrl("/autherror?code=1"); //设置登录页面
        filterFactory.setUnauthorizedUrl("/autherror?code=2"); //授权失败跳转页面
        //4.配置过滤器集合
        /**
         * key :访问连接
         *     支持通配符的形式
         * value:过滤器类型
         *     shiro常用过滤器
         *         anno   :匿名访问(表明此链接所有人可以访问)
         *         authc   :认证后访问(表明此链接需登录认证成功之后可以访问)
         */
        Map<String,String> filterMap = new LinkedHashMap<String,String>();
        //配置请求连接过滤器配置
        //匿名访问(所有人员可以使用)
        filterMap.put("/frame/login", "anon");
        filterMap.put("/autherror", "anon");
        //认证之后访问(登录之后可以访问)
        filterMap.put("/**", "authc");
        //5.设置过滤器
        filterFactory.setFilterChainDefinitionMap(filterMap);
        return filterFactory;
   }
    //配置shiro注解支持
    @Bean
    public AuthorizationAttributeSourceAdvisor
authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new
AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
        }
    //配置shiro redisManager
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        return redisManager;
   }
    //cacheManager缓存 redis实现
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
   }
    /**
     * RedisSessionDAO shiro sessionDao层的实现 通过redis
     * 使用的是shiro-redis开源插件
     */
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
   }
    /**
     * shiro session的管理
     */
    public DefaultWebSessionManager sessionManager() {
        IhrmWebSessionManager sessionManager = new IhrmWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());
        return sessionManager;
   }
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JDK1.9-字节流

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

    cwl_java
  • Java工具集-流水号(GetAutoCode)

    cwl_java
  • JDK1.9-缓冲流

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

    cwl_java
  • 第57节:Java中流的操作以及编码解码

    编码解码:编码时将信息从一种形式变成为另一种形式,成为编码.编码为coding,逆过程为解码.编码时用代码表示的,解码为Decoding,有了编码就有相关的编码...

    达达前端
  • Android Oss上传图片的使用示例

    1.在官网下载 sdk 包 2.解压后得到 jar 包,目前包括 aliyun-oss-sdk-android-x.x.x.jar、okhttp-3.x.x....

    砸漏
  • 深克隆和浅克隆

    Java 集合中提供的拷贝构造函数只支持浅拷贝而不是深拷贝,这是因为集合中的拷贝构造函数是通过引用的复制来达到浅拷贝的。这意味着存储在原有集合和克隆集合中的对象...

    帅飞
  • Java基础—String、StringBuffer、StringBuilder

    我有一个微信公众号,经常会分享一些Java技术相关的干货。如果你喜欢我的分享,可以用微信搜索“Java团长”或者“javatuanzhang”关注。

    Java后端工程师
  • 5.7 VR扫描:AR游戏《星球大战:绝地挑战》现已支持多人游戏模式

    VRPinea
  • spring cloud笔记 oauth2授权服务 默认tokenService配置源码

    路过君
  • 中医+互联网=?腾讯这款AI医学影像产品走进深圳中医院啦!

    传统中医和现代互联网结合,将会擦出怎样的火花?20日,记者从深圳市中医院获悉,近日,人工智能医疗腾讯觅影在该院上线,该院也成为首批微信新农合支付试点医院,传统中...

    企鹅号小编

扫码关注云+社区

领取腾讯云代金券