1. 程式人生 > >Spring Boot2整合Shiro(1):身份認證

Spring Boot2整合Shiro(1):身份認證

Spring Boot2整合Shiro(1):身份認證

 

前言

本文主要介紹了在Spring Boot2專案中整合Shiro實現登入認證。本文假設讀者已經對Shiro和基於RBAC的許可權控制系統有了基本的認識。 
本專案沒有資料庫,也就沒有dao層,所有的使用者和密碼均在Service層採用硬編碼。

特別提醒:因為程式碼塊中的@符號在部落格釋出過程中會導致程式碼格式混亂,所以@都是用雙斜槓註釋了。

建立工程

通過idea的Spring Initializr新建工程spring-boot2-shiro-project,選擇web模組即可。在專案根目錄新增service

controllerconfig資料夾,專案目錄如下圖所示: 
這裡寫圖片描述

封裝統一的返回值

這一步對於整合shiro不是必須的

為了在專案中返回統一的結果,我們新建一個泛型類,包含屬性:結果程式碼code、結果資訊message和結果資料data,其中data採用泛型,程式碼如下:

public class Result<T> {
    private ResultCodeEnum code;
    private String message;
    private  T data;

    public ResultCodeEnum getCode() {
        return code;
    }

    public Result() {
    }

    public Result setCode(ResultCodeEnum resultCode) {
        this.code = resultCode;
        return this;
    }

    public String getMessage() {
        return message;
    }

    public Result setMessage(String message) {
        this.message = message;
        return this;
    }

    public T getData() {
        return data;
    }

    public Result setData(T data) {
        this.data = data;
        return this;
    }
}

再新建一個列舉類ResultCodeEnum,列舉所有的返回程式碼code

public enum ResultCodeEnum {
    SUCCESS(200),//成功
    FAIL(400),//失敗
    UNAUTHORIZED(401),//未認證(簽名錯誤)
    NOT_FOUND(404),//介面不存在
    INTERNAL_SERVER_ERROR(500);//伺服器內部錯誤

    public int code;

    ResultCodeEnum(int code) {
        this.code = code;
    }

    public int getCode() {
        return this.code;
    }
}

再建一個結果生成類ResultGenerator,減少重複程式碼,保證返回值的統一性

public class ResultGenerator {
    private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";

    //成功
    public static Result genSuccessResult() {
        return new Result()
                .setCode(ResultCodeEnum.SUCCESS)
                .setMessage(DEFAULT_SUCCESS_MESSAGE);
    }

    public static <T> Result<T> genSuccessResult(T data) {
        return new Result()
                .setCode(ResultCodeEnum.SUCCESS)
                .setMessage(DEFAULT_SUCCESS_MESSAGE)
                .setData(data);
    }

    public static Result genFailResult(String message) {
        return new Result()
                .setCode(ResultCodeEnum.FAIL)
                .setMessage(message);
    }

    public static Result genUnauthorizedResult() {
        return new Result()
                .setCode(ResultCodeEnum.UNAUTHORIZED)
                .setMessage("許可權不足!");
    }
}

Hello World

新建IndexController

package top.zhaodongxx.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import top.zhaodongxx.result.Result;
import top.zhaodongxx.result.ResultGenerator;

/**
 * <P></P>
 *
 * @author zhaodong
 * @version v1.0
 * @email [email protected]
 * @since 2018/3/30 21:55
 */









@RestController
public class IndexController {

    @GetMapping("/helloworld")
    public Result helloWorld() {

        return ResultGenerator.genSuccessResult("helloworld");
    }
}

執行SpringBoot2ShiroProjectApplication.java即可啟動該Spring Boot專案。 
訪問路徑127.0.0.1:8080/helloworld 
這裡寫圖片描述
專案執行成功!

整合 Shiro

在Spring Boot2專案中整合Shiro主要分為四步

  • 匯入Shiro
  • 建立Shiro配置檔案,並在其中配置DefaultWebSecurityManagerShiroFilterFactoryBean
  • 實現身份認證的具體邏輯Realm
  • 實現登入介面

匯入 Shiro

Shiro提供了一個啟動器來完成與 Spring Boot 的整合。

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

配置 Shiro

因為本節只是介紹身份認證,所以只提供一個最簡單的 Java Class 配置

package top.zhaodongxx.config;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;

/**
 * <P></P>
 *
 * @author zhaodong
 * @version v1.0
 * @email [email protected]
 * @since 2018/3/30 22:41
 */
@Configuration
public class ShiroConfig {
    /**
     * 自定義的Realm
     */
    @Bean(name = "myShiroRealm")
    public MyShiroRealm myShiroRealm(){
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        return myShiroRealm;
    }
    @Bean
    public DefaultWebSecurityManager  securityManager(){
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        設定realm.
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        return shiroFilterFactoryBean;
    }
}

安全管理器DefaultWebSecurityManager是Shiro的核心模組,ShiroFilterFactoryBean用來配置需要被攔截的請求,被攔截下來的請求交給安全管理器管理。myShrioRealm在idea中現在還是紅色的,因為我們還沒有寫。

自定義Shiro

新建一個服務,用來模擬從資料庫中取出使用者資訊,


package top.zhaodongxx.service;

import org.springframework.stereotype.Service;

/**
 * @author zhaodong
 * @version v1.0
 * @email [email protected]
 * @since 2018/3/30 22:59
 */
@Service
public class ShiroService {

    public String getPasswordByUsername(String username){
        switch (username){
            case "liming":
                return "123";
            case "hanli":
                return "456";
            default:
                return null;
        }
    }
}

Realm 是控制認證和授權的核心部分,也是開發人員必須自己實現的部分。 
自定義的Realm通過繼承AuthorizingRealm類,實現它的兩個方法:doGetAuthorizationInfodoGetAuthenticationInfo實現自己的認證和授權邏輯,其中doGetAuthenticationInfo處理授權邏輯暫時不實現。

package top.zhaodongxx.shiro;

import lombok.extern.slf4j.Slf4j;
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 top.zhaodongxx.service.ShiroService;

import javax.annotation.Resource;

/**
 *
 * @author zhaodong
 * @version v1.0
 * @email [email protected]
 * @since 2018/3/30 22:55
 */
public class MyShiroRealm extends AuthorizingRealm {

    @Resource
    public ShiroService shiroService;

    /**
     * 授權
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        return authorizationInfo;
    }

    /**
     * 登入認證
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //獲取使用者賬號
        String username = token.getPrincipal().toString();

        String password = shiroService.getPasswordByUsername(username);
        if (password != null) {
            AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                    username,   //認證通過後,存放在session,一般存放user物件
                    password,   //使用者資料庫中的密碼
                    getName());    //返回Realm名
            return authenticationInfo;
        }
        return null;
    }
}

實現登入介面

AuthenticationException是Shiro封裝的異常,如果登入認證沒有成功就會丟擲這個異常。

package top.zhaodongxx.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import top.zhaodongxx.result.Result;
import top.zhaodongxx.result.ResultGenerator;

/**
 *
 * @author zhaodong
 * @version v1.0
 * @email [email protected]
 * @since 2018/3/30 23:05
 */
@RestController
public class LoginController {
    @PostMapping("/doLogin")
    public Result doLogin(String username, String password) {

        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            subject.login(token);
        } catch (AuthenticationException e) {
            token.clear();
            return ResultGenerator.genFailResult("登入失敗,使用者名稱或密碼錯誤!");
        }
        return ResultGenerator.genSuccessResult("登入成功");
    }
}

測試

重新啟動專案。

通過postman訪問127.0.0.1:8080/doLogin?username=liming&password=1231
登陸失敗

這裡寫圖片描述

訪問127.0.0.1:8080/doLogin?username=liming&password=123
登入成功 
這裡寫圖片描述

至此,通過Shiro實現了身份認證。

專案下載地址

參考資料