spring-boot集成8:集成shiro,jwt

Shrio是一个轻量级的,基于AOP 和 Servlet 过滤器的安全框架。它提供全面的安全性解决方案,同时在 Web 请求级和方法调用级处理身份确认和授权。

JWT(JSON Web Token)是目前最流行的跨域身份验证解决方案,具有加密和自包含的特性。

1.maven配置

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

        <!--jwt-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.2.0</version>
        </dependency>

 

2.自定义Token Authentication

package com.bby.security;

import org.apache.shiro.authc.AuthenticationToken;

/**
 * 自定义jwt类型的token
 *
 * @author: zhangyang
 * @create: 2018/11/28 8:39
 **/
public class MyJWTToken implements AuthenticationToken {
    private String token;

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

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

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

 

3.自定义Shiro过滤器

package com.bby.security;

import com.alibaba.fastjson.JSONObject;
import com.bby.common.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.util.AntPathMatcher;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;


/**
 * jwt token filter
 *
 * @Author zhangyang
 * @Date 下午 8:42 2018/11/27 0027
 **/
@Slf4j
public class MyJWTFilter extends BasicHttpAuthenticationFilter {

    private String tokenHeader;
    private String loginUri;

    public MyJWTFilter(String tokenHeader, String loginUri) {
        this.tokenHeader = tokenHeader;
        this.loginUri = loginUri;
    }

    /**
     * 如果是登录则直接放行;
     * 如果带有 token,则对 token 进行检查
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        HttpServletRequest req = (HttpServletRequest) request;
        AntPathMatcher matcher = new AntPathMatcher();
        // 开放登录接口访问
        if (matcher.match(loginUri, req.getRequestURI())) {
            return true;
        }
        // 判断请求的请求头是否带上token
        if (StringUtils.isBlank(req.getHeader(tokenHeader))) {
            return false;
        }

        // 如果存在,则进入 executeLogin 方法执行登入,检查 token 是否正确
        try {
            executeLogin(request, response);
        } catch (Exception e) {
            log.error(e.getMessage());
            try {
                // globalExceptionHandler无法处理filter中的异常,这里手动处理
                PrintWriter out = response.getWriter();
                out.print(JSONObject.toJSON(Result.failureWithCode(Result.UNAUTHORIZED, e.getMessage())));
                out.flush();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            return false;
        }
        return true;
    }

    /**
     * 执行登陆操作
     *
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader(tokenHeader);
        MyJWTToken jwtToken = new MyJWTToken(token);
        // 提交给realm进行登入,如果错误他会抛出异常并被捕获
        getSubject(request, response).login(jwtToken);
        // 如果没有抛出异常则代表登入成功,返回true
        return true;
    }
}
View Code

 

4.自定义Shiro Realm

package com.bby.security;

import com.bby.common.util.RedisRepository;
import com.bby.mapper.system.SysUserMapper;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 实现AuthorizingRealm接口用户用户认证
 *
 * @author: zhangyang
 * @create: 2018/11/24 21:25
 **/
@Component
public class MyRealm extends AuthorizingRealm {

    @Autowired
    private RedisRepository redisRepository;

    @Autowired
    private SysUserMapper sysUserMapper;

    /**
     * 获取用户权限信息
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 获取token
        String token = (String) principalCollection.getPrimaryPrincipal();
        // 查询用户权限信息
        List<String> permissions = sysUserMapper.getPermissionByUserId(JWTUtil.getUserId(token));

        // 只添加权限(角色方式不灵活)
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        if (CollectionUtils.isNotEmpty(permissions)) {
            simpleAuthorizationInfo.addStringPermissions(permissions);
        }
        return simpleAuthorizationInfo;
    }

    /**
     * 获取用户认证信息
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 加这一步的目的是在Post请求的时候会先进认证,然后在到请求
        Object principal = authenticationToken.getPrincipal();
        if (principal == null) {
            return null;
        }

        String token = (String) principal;
        // 解密获得username,用于和数据库进行对比
        String claim = JWTUtil.getUserId(token);
        // 验证缓存中的登录状态
        if (claim == null
                || !JWTUtil.verify(token, claim)
                || !redisRepository.exists(token)) {
            throw new AuthenticationException("token validation failed");
        }

        // 获取用户信息
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(token, token, getName());
        return simpleAuthenticationInfo;
    }

    /**
     * 设置支持的token类型为自定义jwtToken
     *
     * @param token
     * @return
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof MyJWTToken;
    }

    /**
     * 覆盖验证密码是否匹配的方法,因为在自定义的login方法中已经实现了
     *
     * @param token
     * @param info
     * @throws AuthenticationException
     */
    @Override
    protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
    }
}
View Code

 

5.登录代码

package com.bby.controller;

import com.bby.common.vo.Result;
import com.bby.security.JWTUtil;
import com.bby.service.system.ISysUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * 登录/鉴权controller
 *
 * @author: zhangyang
 * @create: 2018/11/25 20:26
 **/
@Api(tags = "认证授权")
@RestController
@RequestMapping("auth")
public class AuthController {

    @Autowired
    private ISysUserService sysUserService;

    /**
     * 登录
     *
     * @param authInfo
     * @return
     */
    @ApiOperation("登录")
    @PostMapping("login")
    public Result login(@ApiParam("用户名") @RequestBody Map<String, String> authInfo) {
        return sysUserService.validate(authInfo.get("username"), authInfo.get("password"));
    }

    /**
     * 登出
     *
     * @return
     */
    @ApiOperation("登出")
    @PostMapping("logout")
    public Result logout(HttpServletRequest request) {
        // 因为token的方式是无状态的,要实现登出,则需要使用缓存来保存状态
        return sysUserService.logout(JWTUtil.getToken(request));
    }

    /**
     * 用户信息
     * 用户名:
     * @return
     */
    @ApiOperation("获取用户信息")
    @GetMapping("info")
    public Result info(HttpServletRequest request) {
        return sysUserService.getAuthInfo(JWTUtil.getUserId(JWTUtil.getToken(request)));
    }
}
View Code

 

6.shiro配置

package com.bby.security;

import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map;

/**
 * shiro 配置
 *
 * @author: zhangyang
 * @create: 2018/11/24 21:34
 **/
@Configuration
public class ShiroConfiguration {
    @Value("${jwt.token-header}")
    private String tokenHeader;

    @Value("${jwt.filter-name}")
    private String jwtFilterName;

    @Value("${login.uri}")
    private String loginUri;


    /**
     * 将自己的验证方式加入容器
     *
     * @return
     */
    @Autowired
    private MyRealm myRealm;

    /**
     * 权限管理,配置主要是Realm的管理认证
     *
     * @return
     */
    @Bean
    @DependsOn("myRealm")
    public SecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        // 使用自己的realm
        System.out.println(myRealm);
        manager.setRealm(myRealm);

        // 关闭shiro自带的session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        manager.setSubjectDAO(subjectDAO);
        return manager;
    }

    /**
     * Filter工厂,设置对应的过滤条件和跳转条件
     *
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        // 添加自己的过滤器并且取名为jwt
        Map<String, Filter> filterMap = new HashMap<>();
        System.out.println(tokenHeader);
        filterMap.put("jwt", new MyJWTFilter(tokenHeader, loginUri));
        factoryBean.setFilters(filterMap);

        factoryBean.setSecurityManager(securityManager);
        factoryBean.setUnauthorizedUrl("/401");

        Map<String, String> filterRuleMap = new HashMap<>();
        // 所有请求通过我们自己的JWT Filter
        filterRuleMap.put("/**", "jwt");
        // 访问401和404页面不通过我们的Filter
        filterRuleMap.put("/401", "anon");
        factoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return factoryBean;
    }

    /**
     * 下面的代码是添加注解支持
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 强制使用cglib,防止重复代理和可能引起代理出错的问题
        // https://zhuanlan.zhihu.com/p/29161098
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}
View Code

 

posted @ 2018-12-14 16:27  zhya_hopeful  阅读(709)  评论(0编辑  收藏  举报