Spring Boot2整合Shiro(1):身份认证

前言

本文主要介绍了在Spring Boot2项目中整合Shiro实现登录认证。本文假设读者已经对Shiro和基于RBAC的权限控制系统有了基本的认识。
本项目没有数据库,也就没有dao层,所有的用户和密码均在Service层采用硬编码。

特别提醒:因为代码块中的@符号在博客发布过程中会导致代码格式混乱,所以@都是用双斜杠注释了。

创建工程

通过 idea的Spring Initializr新建工程spring-boot2-shiro-project,选择web模块即可。在项目根目录添加servicecontrollerconfig文件夹,项目目录如下图所示:
项目目录

封装统一的返回值

这一步对于整合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 zhaodongxx@outlook.com
 * @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

2.jpg

项目运行成功!

整合 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 zhaodongxx@outlook.com
 * @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 zhaodongxx@outlook.com
 * @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 zhaodongxx@outlook.com
 * @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 zhaodongxx@outlook.com
 * @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,
登陆失败
3.png
访问127.0.0.1:8080/doLogin?username=liming&password=123,
登录成功
3.png

至此,通过Shiro实现了身份认证。

项目下载地址

参考资料