SpringBoot整合Shiro 之 用户名密码登录
首先 使用Shiro 我们要大致了解清楚 什么是Shiro?它能帮我们实现什么功能?
根据官方文档介绍:Shiro 是一个功能强大且易于使用的Java安全框架,可以实现 身份验证、授权、加密和会话管理功能。
那么今天 我这里就主要 实现以下 身份验证功能,也就是用户登录啦。
1.数据库 这里提供一个简单 user表SQL
CREATE TABLE `ums_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`login_name` varchar(15) NOT NULL COMMENT '用户名',
`pass_word` varchar(255) NOT NULL COMMENT '密码',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户表';
2.导入 相关依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.BUILD-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zh.shiro</groupId>
<artifactId>springboot_shiro</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.代码编写
3.1 连接数据库配置
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/jintian?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowPublicKeyRetrieval=true&verifyServerCertificate=false&useSSL=false # 数据库名称
username: root
password: 123456
dbcp2:
min-idle: 5 # 数据库连接池的最小维持连接数
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
3.2 创建用户实体类
import java.time.LocalDateTime;
/**
* @Author: zh
* @Date: 2020/7/7 10:59
*/
public class User {
private Long id;
private String loginName;
private String passWord;
private LocalDateTime createTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getLoginName() {
return loginName;
}
public void setLoginName(String loginName) {
this.loginName = loginName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
}
3.3 创建 编写 UserMapper.java 和 UserMapper.xml
UserMapper.java
import com.zh.shiro.pojo.User;
import org.apache.ibatis.annotations.Param;
/**
* @Author: zh
* @Date: 2020/7/7 11:08
*/
public interface UserMapper {
/**
* 根据用户名查询 该用户
* @return
*/
User getUserByName(@Param("name") String name);
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zh.shiro.mapper.UserMapper">
<select id="getUserByName" resultType="com.zh.shiro.pojo.User">
SELECT
id,
login_name AS 'loginName',
pass_word AS 'passWord'
FROM
ums_user
WHERE
login_name = #{name}
</select>
</mapper>
3.4 自定义Realm 是用来查询用户信息 保存到权限管理器(可保存 用户对应 的角色 和 权限信息)
CustomAuthorizingRealm.java
import com.zh.shiro.mapper.UserMapper;
import com.zh.shiro.pojo.User;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
/**
* @Author: zh
* @Date: 2020/7/7 11:20
*/
public class CustomAuthorizingRealm extends AuthorizingRealm {
@Autowired
private UserMapper userMapper;
/**
* AuthorizationInfo 权限认证
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
/**
*AuthenticationInfo 身份验证
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取 前端传过来的用户名和密码
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
String username = usernamePasswordToken.getUsername();
String password = new String(usernamePasswordToken.getPassword());
// 效验用户名和密码是否为空
if (StringUtils.isEmpty(username)){
throw new NullPointerException("用户名不能为空");
}
if (StringUtils.isEmpty(password)){
throw new NullPointerException("密码不能为空");
}
// 根据 当前用户名 获取数据库 用户信息
User user = userMapper.getUserByName(username);
if (null == user){
throw new NullPointerException("该用户不存在");
}
// principal:认证的实体信息,可以是username,也可以是数据库表对应的用户的实体对象
Object principal = user;
// shiro 效验凭证
Object credentials = user.getPassWord();
// 加盐 用户密码加密时用到
ByteSource credentialsSalt = ByteSource.Util.bytes("zh");
return new SimpleAuthenticationInfo(principal,credentials,credentialsSalt,this.getClass().getName());
}
}
3.5 配置 ShiroConfig
ShiroConfig.java
import com.zh.shiro.realm.CustomAuthorizingRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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 java.util.LinkedHashMap;
import java.util.Map;
/**
* @Author: zh
* @Date: 2020/7/7 11:34
*/
@Configuration
public class ShiroConfig {
/**
* shiroFilter 过滤
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 需要登陆的接口 访问未登录 或 token失效时 回调setLoginUrl里的接口
shiroFilterFactoryBean.setLoginUrl("/api/401");
// 登录成功后回调路径 前后端分离项目不必要
// shiroFilterFactoryBean.setSuccessUrl("/");
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
/**
* anon 匿名也可以访问 ps:没登录就能访问
* authc 需要登录以后才可以访问
* 过滤链 是由上而下执行的 所以 一般把 /** 放在最下面
*/
filterChainDefinitionMap.put("/api/test","authc");
filterChainDefinitionMap.put("/**","anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 配置 安全管理器
* @return
*/
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(customAuthorizingRealm());
return securityManager;
}
/**
* 将自定义realm 加入容器
* @return
*/
@Bean
public CustomAuthorizingRealm customAuthorizingRealm(){
CustomAuthorizingRealm realm = new CustomAuthorizingRealm();
realm.setCredentialsMatcher(hashedCredentialsMatcher());
return realm;
}
/**
*定义密码加密规则
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 设置散列算法
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 设置双层MD5 散列次数
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
}
3.6 编写 UserController 因为没有 封装 返回结果集 所以 就直接返回的Map 凑合凑合吧 哈哈
UserController.java
import com.zh.shiro.pojo.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: zh
* @Date: 2020/7/7 11:49
*/
@RestController
@RequestMapping("/api")
public class UserController {
/**
* 用户登录接口
* @param user
* @return
*/
@PostMapping("/login")
public Object LigIn(@RequestBody User user){
Map<String,Object> map = new HashMap<>();
Subject subject = SecurityUtils.getSubject();
try {
UsernamePasswordToken token = new UsernamePasswordToken(user.getLoginName(),user.getPassWord());
subject.login(token);
map.put("code","200");
map.put("message","success");
map.put("token",subject.getSession().getId());
return map;
}catch (IncorrectCredentialsException e){
map.put("code","501");
map.put("message","密码不正确");
return map;
}
}
/**
* 用户未登录 token失效时 访问其他接口时 shiroFilter 调用接口
* @return
*/
@GetMapping("/401")
public Object _401(){
Map<String,Object> map = new HashMap<>();
map.put("code","401");
map.put("message","not login");
return map;
}
/**
* 测试接口
* @return
*/
@GetMapping("/test")
public Object test(){
Map<String,Object> map = new HashMap<>();
map.put("code","200");
map.put("message","success");
return map;
}
}
3.7 测试环节 使用 postman测试
- 先给数据库插入一条数据。因为 这里密码使用 MD5 加密了。所以我们先在代码里整一个测试密码出来。
- 数据库
- 准备好了 就开始可以测试了
再来 使用登录状态调用一下 test 接口
未登录状态调用 清掉 postman 里保存的token
这里就可以看到 他回调的是 401 接口
个人感觉 这个springBoot 整合 shiro还是比较全的。 但只是单独的用户的登录认证,没有涉及到 权限之类的。所以权限 就留到下次吧。
代码已上传至 我的码云