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

Shrio是一個輕量級的,基於AOP 和 Servlet 過濾器的安全框架。它提供全面的安全性解決方案,同時在 Web 請求級和方法調用級處理身份確認和受權。java

JWT(JSON Web Token)是目前最流行的跨域身份驗證解決方案,具備加密和自包含的特性。web

1.maven配置redis

<!-- 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 Authenticationspring

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 Realmapache

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.登陸代碼json

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
相關文章
相關標籤/搜索