1. 程式人生 > >springboot2+shiro+jwt整合(一)登入認證

springboot2+shiro+jwt整合(一)登入認證

當我們要把伺服器做成無狀態時(即伺服器端不會儲存session),這裡我們就可以用到JWT

為什麼使用JWT?

1.簡潔(Compact): 可以通過URL,POST引數或者在HTTP header傳送,因為資料量小,傳輸速度也很快。

2.自包含(Self-contained):負載中包含了所有使用者所需要的資訊,避免了多次查詢資料庫。

3.安全(security): 與簡單的JSON相比,XML和XML數字簽名會引入複雜的安全漏洞。

認證原理

1.使用者登陸之後,使用密碼對賬號進行簽名生成並返回token並設定過期時間;

2.將token儲存到本地,並且每次傳送請求時都在header上攜帶token。

3.shiro過濾器攔截到請求並獲取header中的token,並提交到自定義realm的doGetAuthenticationInfo方法。

4.通過jwt解碼獲取token中的使用者名稱,從資料庫中查詢到密碼之後根據密碼生成jwt效驗器並對token進行驗證。

OK,介紹完後上程式碼。

JWT

引入pom

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

首先我們需要自定義一個物件用來包裝token。

JwtToken

package com.style.orange.shiro;

import org.apache.shiro.authc.AuthenticationToken;

/**
 * @author Mr.Li
 * @create 2018-07-12 15:19
 * @desc
 **/
public class JwtToken implements AuthenticationToken {

    private String token;

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

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

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

再寫一個工具類用來進行簽名和效驗Token

JwtToken

package com.style.orange.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Date;

/**
 * @author Mr.Li
 * @create 2018-07-12 14:23
 * @desc JWT工具類
 **/
public class JwtUtil {

    private static final long EXPIRE_TIME = 5 * 60 * 1000;

    /**
     * 校驗token是否正確
     *
     * @param token  金鑰
     * @param secret 使用者的密碼
     * @return 是否正確
     */
    public static boolean verify(String token, String username, String secret) {
        try {
            //根據密碼生成JWT效驗器
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("username", username)
                    .build();
            //效驗TOKEN
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception exception) {
            return false;
        }
    }

    /**
     * 獲得token中的資訊無需secret解密也能獲得
     *
     * @return token中包含的使用者名稱
     */
    public static String getUsername(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 生成簽名,5min後過期
     *
     * @param username 使用者名稱
     * @param secret   使用者的密碼
     * @return 加密的token
     */
    public static String sign(String username, String secret) {
        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        Algorithm algorithm = Algorithm.HMAC256(secret);
        // 附帶username資訊
        return JWT.create()
                .withClaim("username", username)
                .withExpiresAt(date)
                .sign(algorithm);

    }
}

前面認證原理說到我們要使用shiro來攔截token,那就需要我們自己寫一個jwt過濾器來作為shiro的過濾器。

JwtFilter

import com.style.orange.shiro.JwtToken;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author Mr.Li
 * @create 2018-07-12 15:56
 * @desc
 **/
public class JwtFilter extends BasicHttpAuthenticationFilter {


    /**
     * 執行登入認證
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        try {
            executeLogin(request, response);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     *
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("Authorization");

        JwtToken jwtToken = new JwtToken(token);
        // 提交給realm進行登入,如果錯誤他會丟擲異常並被捕獲
        getSubject(request, response).login(jwtToken);
        // 如果沒有丟擲異常則代表登入成功,返回true
        return true;
    }



    /**
     * 對跨域提供支援
     */
    @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,OPTIONS,PUT,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);
    }
}

Shiro

引入pom

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

準備自定義Realm

MyRealm

package com.style.orange.shiro;

import com.style.orange.model.SysUser;
import com.style.orange.service.SysUserService;
import com.style.orange.utils.JwtUtil;
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;

/**
 * @author Mr.Li
 * @create 2018-07-12 15:23
 * @desc
 **/
@Component
public class MyRealm extends AuthorizingRealm {

    @Autowired
    private SysUserService sysUserService;


    /**
     * 必須重寫此方法,不然Shiro會報錯
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /**
     * 只有當需要檢測使用者許可權的時候才會呼叫此方法,例如checkRole,checkPermission之類的
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = JwtUtil.getUsername(principals.toString());
        SysUser user = sysUserService.findByUserName(username);
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        return simpleAuthorizationInfo;
    }

    /**
     * 預設使用此方法進行使用者名稱正確與否驗證,錯誤丟擲異常即可。
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
        String token = (String) auth.getCredentials();
        // 解密獲得username,用於和資料庫進行對比
        String username = JwtUtil.getUsername(token);
        if (username == null) {
            throw new AuthenticationException("token無效");
        }

        SysUser userBean = sysUserService.findByUserName(username);
        if (userBean == null) {
            throw new AuthenticationException("使用者不存在!");
        }

        if (!JwtUtil.verify(token, username, userBean.getPassword())) {
            throw new AuthenticationException("使用者名稱或密碼錯誤");
        }

        return new SimpleAuthenticationInfo(token, token, "my_realm");
    }
}

ShiroConfig

package com.style.orange.shiro;

import com.style.orange.filter.JwtFilter;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

/**
 * @author: PENG
 * @date: 2018/2/7
 * @description: shiro 配置類
 */

@Configuration
public class ShiroConfig {

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //攔截器
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 配置不會被攔截的連結 順序判斷
        filterChainDefinitionMap.put("/login/**", "anon");
        filterChainDefinitionMap.put("/**.js", "anon");
        filterChainDefinitionMap.put("/druid/**", "anon");
        filterChainDefinitionMap.put("/swagger**/**", "anon");
        filterChainDefinitionMap.put("/webjars/**", "anon");
        filterChainDefinitionMap.put("/v2/**", "anon");
        // 新增自己的過濾器並且取名為jwt
        Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
        filterMap.put("jwt", new JwtFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        //<!-- 過濾鏈定義,從上向下順序執行,一般將/**放在最為下邊
        filterChainDefinitionMap.put("/**", "jwt");


        //未授權介面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }

    @Bean("securityManager")
    public SecurityManager securityManager(MyRealm myRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);

        /*
         * 關閉shiro自帶的session,詳情見文件
         * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);

        return securityManager;
    }
}

OK,接下來我們進行測試,我這裡集成了swagger2,測試起來比較方便。

如圖紅色標註就是返回的token,之後我把token放到請求的header中訪問就可以了通過了。

相關推薦

springboot2+shiro+jwt整合登入認證

當我們要把伺服器做成無狀態時(即伺服器端不會儲存session),這裡我們就可以用到JWT。 為什麼使用JWT? 1.簡潔(Compact): 可以通過URL,POST引數或者在HTTP header傳送,因為資料量小,傳輸速度也很快。 2.自包含(Self-conta

springboot2+shiro+jwt整合細粒度許可權控制+使用redis作為快取

簡單來說,當專案啟動起來後,我們的後臺介面的許可權控制就應該起作用了,那麼如何使用shiro來實現呢?我這裡使用的是 如何使用註解來配置細粒度許可權。 首先,shiro預設不支援使用註解方式,需要在ShiroConfig中新增以下程式碼 /** * 下面

深入淺出學Shiro--登入認證

ApacheShiro是一個強大易用的Java安全框架,提供了認證、授權、加密和會話管理等功能: Shiro為解決下列問題,提供了保護應用的API:   認證 - 使用者身份識別,常被稱為使用者“登入”;   授權 - 訪問控制;   密碼加密 - 保護或隱藏資料防止被

shiro spring整合

shiro介紹 (1)簡介 Apache Shiro是Java的一個安全框架。目前,使用Apache Shiro的人越來越多,因為它相當簡單,對比Spring Security,可能沒有Spring Security做的功能強大,但是在實際工作時可能並不需要

shiro許可權控制shiro介紹以及整合SSM框架

 shiro安全框架是目前為止作為登入註冊最常用的框架,因為它十分的強大簡單,提供了認證、授權、加密和會話管理等功能 。  shiro能做什麼?       認證:驗證使用者的身份       授權:對使用者執行訪問控制:判斷使用者是否被允許做某事       會話管理:在任

Apache Shiro 使用手冊Shiro架構介紹

springmvc+mybatis dubbo+zookeeper restful redis分布式緩存 shiro kafka 一、什麽是Shiro Apache Shiro是一個強大易用的Java安全框架,提供了認證、授權、加密和會話管理等功能: 認證 - 用戶身份識別,常被稱為用戶“

spring boot2 整合Mybatis 特別完整!

one OS solver mapper ant gin enabled selectall pom 大概介紹下流程: 借助idea實現mybatis逆向工程 用xml配置實現整合 用cmd命令行實現mybatis逆向工程 用mapping.xml配置實現數據交互

SSM整合:創建項目

SSM這裏應用的工具是Eclipse+Maven,jdk版本1.8.x 這裏直接新建maven項目就好了,webapp1.1 主要貼一下pom文件,這個文件其實是循序漸進的。http://mvnrepository.com/這是個神奇的網站! <project xmlns="http://maven.a

python實戰演練登入介面程式

一.實現功能: 1. 使用者輸入帳號密碼進行登陸2. 使用者資訊儲存在檔案內3. 使用者密碼輸入錯誤三次後鎖定使用者 二.流程圖   三.程式碼 #-*- Coding:utf-8 -*- # Author: kking # 建立了user_info是使用者檔案,user_lo

Shiro學習筆記--- 認證與授權

一、簡介 Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、授權、密碼和會話管理。使用Shiro的易於理解的API,您可以快速、輕鬆地獲得任何應用程式,從最小的移動應用程式到最大的網路和企業應用程式。 主要功能 三個核心元件:Subject, Security

Shiro的學習——Shiro介紹

一、介紹 shiro是apache的一個開源框架,是一個許可權管理的框架,實現 使用者認證(登入)、使用者授權。 它將軟體系統的安全認證相關的功能抽取出來,實現使用者身份認證,許可權授權、加密、會話管理等功能,組成了一個通用的安全認證框架。 與其一樣的還有SpringSecurit

getFieldDecorator用法——登入表單

之前使用antd的ui表單,卻沒發現這麼好用的用法,推薦給大家 import React from "react"; import { Card, Form, Input, Button, message, Icon, Checkbox } from "antd"; con

機房收費系統------登入&修改

前言 進行機房也有一段時間了,一直處於走迷宮的狀態不知從何入手。從生活中出發,我們在使用某個系統時,一般情況下最先進入該系統的登入頁面。現在輪到自己敲系統了,當然也是從登入開始啦。 正文 機房收費系統登入窗體的思路,和有原始碼的學生系統的思路是大同

從Elasticsearch詳解Ambari與第三方軟體的整合

一. 簡單介紹 1. 軟體介紹 1)Ambari(HDP) 玩過大資料的人都知道,除了原生的apache hadoop,有兩大hadoop廠商(現在已經合併了。。喜聞樂見。。。):Hortonworks 和 Cloudera。Cloudera的hadoop產品相對來講成

spring-boot2-整合Mybatis-特別完整!

整合Mybatis分為兩種模式,一種是xml配置,一種是註解。(類似JPA) 我在這裡重點放在xml配置上,因為如果想用註解的話,建議直接用jpa代替,因為Jpa有更成熟的CRUD介面更方便開發。我在後文中也會把註解方式說清楚。 大概介紹下流程:

Shiro系列專題-什麼是RBAC模型?

       角色訪問控制(RBAC)引入了Role的概念,目的是為了隔離User(即動作主體,Subject)與Privilege(許可權,表示對Resource的一個操作,即Operation+Resource)。 Role作為一個使用者(User)與許可權(Privil

SpringBoot2.0填坑:使用CROS解決跨域並解決swagger 訪問不了問題

簡介 公司後臺是採用SpringBoot2.0 搭建的微服務架構,前端框架用的是vue 使用前後端分離的開發方式,在開發聯調的時候需要進行跨域訪問,那麼使用CROS解決了跨域問題,但是swagger 卻用不了 具體解決方案請繼續往下看… CROS跨域原理 跨域資源共享(CORS)

騰訊Tinker 熱修復 Andriod studio 3.0 配置和整合

本文說明 面試的時候經常問我有沒有用過熱修復?用誰的?能說下原理嗎?當時我回答得不好,畢竟以前的專案都沒有用,又不敢裝逼,mmp,但是基本流程還是知道的,所以我們來初探下Tinker 這個熱修復,如果我是Andriod studio 2.3的話,我還不怎麼想寫這個文章,畢竟太多了,沒有

Eclipse與GitHub的整合——本地Git倉庫中的程式碼push至GitHub

團隊合作開發一個專案的時候,使用Git版本控制,將程式碼託管到GitHub上對多人合作是非常方便的。下面介紹一下Eclipse與GitHub的整合——本地Git倉庫程式碼push到GitHub上。 前提條件: 1. 本地已安裝Git 2. 有GitHub賬號 3. 開發

apache shiro叢集實現 session共享

    Apache Shiro的基本配置和構成這裡就不詳細說明了,其官網有說明文件,這裡僅僅說明叢集的解決方案,詳細配置:shiro web config     Apache Shiro叢集要解決2個問題,一個是session的共享問題,一個是授權資訊的cache共享問