Spring Boot2整合Shiro(1):身份认证
前言
本文主要介绍了在Spring Boot2项目中整合Shiro实现登录认证。本文假设读者已经对Shiro和基于RBAC的权限控制系统有了基本的认识。
本项目没有数据库,也就没有dao层,所有的用户和密码均在Service层采用硬编码。
特别提醒:因为代码块中的
@
符号在博客发布过程中会导致代码格式混乱,所以@
都是用双斜杠注释了。
创建工程
通过 idea的Spring Initializr
新建工程spring-boot2-shiro-project
,选择web模块即可。在项目根目录添加service
、controller
和config
文件夹,项目目录如下图所示:
封装统一的返回值
这一步对于整合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
项目运行成功!
整合 Shiro
在Spring Boot2项目中整合Shiro主要分为四步
- 导入Shiro
- 创建Shiro配置文件,并在其中配置
DefaultWebSecurityManager
和ShiroFilterFactoryBean
- 实现身份认证的具体逻辑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
类,实现它的两个方法:doGetAuthorizationInfo
和doGetAuthenticationInfo
实现自己的认证和授权逻辑,其中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
,
登陆失败
访问127.0.0.1:8080/doLogin?username=liming&password=123
,
登录成功
至此,通过Shiro实现了身份认证。
项目下载地址
- github: spring-boot2-shiro-project