Shiro认证集成

1、介绍

Shiro是开源的JAVA安全框架,实现用户身份认证、权限授权、加密、会话管理等功能。与Spring Security相比,更加轻量级。

2、pom依赖

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>

3、核心配置类

@Configuration
public class ShiroConfig {

    @Resource
    private ShiroRealm shiroRealm;

    @Autowired
    private ShiroAnonConfig shiroAnonConfig;

    @Bean
    public ShiroFilterFactoryBean shirFilter(@Qualifier("securityManager") SecurityManager securityManager, RedisTemplate<String,String> redisTemplate) {
        //创建拦截链实例
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //设置拦截链map
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 放过自定义白名单连接
        shiroAnonConfig.getAnonUri().forEach((uri) -> {
            filterChainDefinitionMap.put(uri, "anon");
        });
        filterChainDefinitionMap.put("/**", "token");
        //设置拦截规则给shiro的拦截链工厂
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        // 添加自己的自定义拦截器
        Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
        filterMap.put("token", new ShiroTokenFilter(redisTemplate));
        shiroFilterFactoryBean.setFilters(filterMap);
        //配置拦截链到过滤器工厂
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        //返回实例
        return shiroFilterFactoryBean;

    }


    @Bean
    public DefaultWebSecurityManager securityManager() {
        //创建默认的web安全管理器
        DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
        //配置shiro的自定义认证逻辑
        defaultSecurityManager.setRealm(shiroRealm);
        //关闭shiro自带的session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        defaultSecurityManager.setSubjectDAO(subjectDAO);
        //返回安全管理器实例
        return defaultSecurityManager;
    }

    /**
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions)
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 开启aop注解支持
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor
                = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

4、核心Relm

自定义token

public class ShiroToken implements AuthenticationToken {

    private String token;

    public ShiroToken(String token) {
        this.token = token;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }

}

Relm

@Slf4j
@Component
public class ShiroRealm extends AuthorizingRealm {

    @Resource
    private UserDetailsService userDetailsService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 设置对应的token类型
     * 必须重写此方法,不然Shiro会报错
     *
     * @param token 令牌
     * @return boolean
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof ShiroToken;
    }

    /**
     * 授权认证
     *
     * @param principals 主要收集
     * @return {@link AuthorizationInfo}
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        User userAuthInfoDTO = (User) principals.getPrimaryPrincipal();
        log.info("---->用户token信息缓存数据:{}", userAuthInfoDTO);
        if (null == userAuthInfoDTO) {
            return null;
        }
        List<String> authoritiesList = userDetailsService.getAuthoritiesList(userAuthInfoDTO.getId());
        if (CollectionUtils.isEmpty(authoritiesList)) {
            return null;
        } else {
            //设置权限
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            info.setStringPermissions(new HashSet<>(authoritiesList));
            //返回权限实例
            return info;
        }
    }

    /**
     * 认证
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
            throws AuthenticationException {
        String token = (String) authenticationToken.getCredentials();
        String key = "LOGIN-" + token;
        if (!redisTemplate.hasKey(key)) {
            return null;
        }
        String userJson = redisTemplate.opsForValue().get(key);
        User user = JSON.parseObject(userJson, User.class);
        if (Objects.nonNull(user)) {
            return new SimpleAuthenticationInfo(user, token, "shiroRealm");
        }
        return null;
    }

}

5、核心filter

@Slf4j
public class ShiroTokenFilter extends BasicHttpAuthenticationFilter implements Filter {


    private RedisTemplate<String, String> redisTemplate;

    public ShiroTokenFilter(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 执行登录
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws IOException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("token");
        log.info("当前请求token:{}", token);
        if (token == null) {
            return false;
        }
        ShiroToken shiroToken = new ShiroToken(token);
        // 提交给realm进行登入,如果错误他会抛出异常并被捕获
        try {
            getSubject(request, response).login(shiroToken);
            // 如果没有抛出异常则代表登入成功,返回true
            return true;
        } catch (Exception e) {
            log.info("token鉴权异常", e);
            return false;
        }
    }

    /**
     * 执行登录认证
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        try {
            return executeLogin(request, response);
        } catch (IOException e) {
            return false;
        }
    }

    /**
     * 认证失败时,自定义返回json数据
     *
     * @param request  请求
     * @param response 响应
     * @return boolean* @throws Exception 异常
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        ResponseResult responseResult = new ResponseResult(500, "鉴权失败");
        Object parse = JSONObject.toJSON(responseResult);
        response.setCharacterEncoding("utf-8");
        response.getWriter().print(parse);
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        boolean bool = super.onAccessDenied(request, httpServletResponse);
        httpServletResponse.setContentType("application/json");
        //设置后,浏览器不会弹出用户验证
        httpServletResponse.setHeader("WWW-Authenticate", "");
        return bool;
    }

    /**
     * 对跨域提供支持
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,PATCH,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers",
                httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }
}

6、验证

登录登出

@Service
@Slf4j
public class LoginServiceImpl implements LoginServcie {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Override
    public ResponseResult login(User user) {
        try {
            String userName = user.getUserName();
            String password = user.getPassword();
            User userByName = userDetailsService.getUserByName(userName);
            if (Objects.isNull(userByName)) {
                return new ResponseResult(500, "用户不存在");
            }
            if (userByName.getPassword().equals(password)) {
                String token = UUID.randomUUID().toString().replaceAll("-", "");
                redisTemplate.opsForValue().set("LOGIN-" + token, JSON.toJSONString(userByName), 1, TimeUnit.HOURS);
                Map<String, String> result = new HashMap<>();
                result.put("token", token);
                return new ResponseResult(200, "登录成功", result);
            }
        } catch (Exception e) {
            log.error("login error", e);
        }
        return new ResponseResult(500, "登陆失败");
    }

    @Override
    public ResponseResult logout() {
        Subject currentUser = SecurityUtils.getSubject();
        if (currentUser.isAuthenticated()) {
            currentUser.logout();
        }
        return new ResponseResult(200, "退出成功");
    }
}

权限控制-shiro注解

@RestController
public class HelloController {

    @GetMapping("/hello")
    @RequiresPermissions("userPerm")
    public String hello() {
        return "hello";
    }

    @GetMapping("/hello2")
    @RequiresPermissions("dataPerm")
    public String hello2() {
        Subject subject = SecurityUtils.getSubject();
        User principal = (User) subject.getPrincipal();
        return "hello2";
    }
}

7、总结

1、输入用户名密码登录,此登录跟shiro无关,校验用户名密码,成功之后生成token,redis根据此token保存用户信息.

2、接口调用,shiro认证,进入到ShiroTokenFilter,拼接ShiroToken,调用ShiroRealm->doGetAuthenticationInfo,redis中存在此token信息,拼装SimpleAuthenticationInfo,认证成功。

3、shiro授权,在认证成功后,进入到ShiroRealm->doGetAuthorizationInfo,拼装SimpleAuthorizationInfo,生成权限信息,再跟RequiresPermissions需要的权限进行匹配

results matching ""

    No results matching ""