Shiro框架快速学习札记
⼀、权限的管理
1.1 什么是权限管理
基本上涉及到⽤⼾参与的系统都要进⾏权限管理,权限管理属于系统安全的范畴,权限管理实现对⽤⼾访问系统的控制
,按照安全规则或者安全策略控制⽤⼾可以访问⽽且只能访问⾃⼰被授权的资源。
权限管理包括⽤⼾⾝份认证
和授权
两部分,简称认证授权
。对于需要访问控制的资源⽤⼾⾸先经过⾝ 份认证,认证通过后⽤⼾具有该资源的访问权限⽅可访问。
1.2 什么是⾝份认证
⾝份认证
,就是判断⼀个⽤⼾是否为合法⽤⼾的处理过程。
最常⽤的简单⾝份认证⽅式是系统通过核对⽤⼾输⼊的⽤⼾名和⼝令,看其是否与系统中存储的该⽤⼾的⽤⼾名和⼝令⼀致,来判断⽤⼾⾝份是否正确。对于采⽤指纹等系统,则出⽰指纹;对于硬件Key等刷卡系统,则需要刷卡。
1.3 什么是授权
授权,即访问控制
,控制谁能访问哪些资源。主体进⾏⾝份认证后需要分配权限⽅可访问系统的资源,对于某些资源没有权限是⽆法访问的.
⼆、什么是Shiro
Apache Shiro™
is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-tounderstand API, you can quickly and easily secure any application ‒ from the smallest mobile applications to the largest web and enterprise applications.
Shiro 是⼀个功能强⼤且易于使⽤的Java安全框架,它执⾏⾝份验证、授权、加密和会话管理
。使⽤ Shiro易于理解的API,您可以快速轻松地保护任何应⽤程序—从最⼩的移动应⽤程序到最⼤的web和 企业应⽤程序。
三、Shiro的核⼼架构
3.1 Subject
Subject即主体,外部应⽤与subject进⾏交互,subject记录了当前操作⽤⼾,将⽤⼾的概念理解为当前操作的主体,可能是⼀个通过浏览器请求的⽤⼾,也可能是⼀个运⾏的程序。
Subject在shiro中是⼀个接⼝,接⼝中定义了很多认证授相关的⽅法,外部程序通过subject进⾏认证 授,⽽subject是通过SecurityManager安全管理器进⾏认证授权
3.2 SecurityManager
SecurityManager即安全管理器,对全部的subject进⾏安全管理,它是shiro的核⼼,负责对所有的 subject进⾏安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上 SecurityManager是通过Authenticator进⾏认证,通过Authorizer进⾏授权,通过SessionManager 进⾏会话管理等。
SecurityManager是⼀个接⼝,继承了Authenticator, Authorizer, SessionManager这三个接⼝。
3.3 Authenticator
Authenticator即认证器,对⽤⼾⾝份进⾏认证,Authenticator是⼀个接⼝,shiro提供 ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满⾜⼤多数需 求,也可以⾃定义认证器。
3.4 Authorizer
Authorizer即授权器,⽤⼾通过认证器认证通过,在访问功能时需要通过授权器判断⽤⼾是否有此功 能的操作权限。
3.5 Realm
Realm即领域,相当于datasource数据源,securityManager进⾏安全认证需要通过Realm获取⽤⼾ 权限数据,⽐如:如果⽤⼾⾝份数据在数据库那么realm就需要从数据库获取⽤⼾⾝份信息。
注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。
3.6 SessionManager
sessionManager即会话管理,shiro框架定义了⼀套会话管理,它不依赖web容器的session,所以 shiro可以使⽤在⾮web应⽤上,也可以将分布式应⽤的会话集中在⼀点管理,此特性可使它实现单点登录。
3.7 SessionDAO
SessionDAO即会话dao,是对session会话操作的⼀套接⼝,⽐如要将session存储到数据库,可以通 过jdbc将会话存储到数据库。
3.8 CacheManager
CacheManager即缓存管理,将⽤⼾权限数据存储在缓存,这样可以提⾼性能。
3.9 Cryptography
Cryptography即密码管理,shiro提供了⼀套加密/解密的组件,⽅便开发。⽐如提供常⽤的散列、加/ 解密等功能。
四、Shiro中的认证
4.1 认证
⾝份认证,就是判断⼀个⽤⼾是否为合法⽤⼾的处理过程。最常⽤的简单⾝份认证⽅式是系统通过核 对⽤⼾输⼊的⽤⼾名和⼝令,看其是否与系统中存储的该⽤⼾的⽤⼾名和⼝令⼀致,来判断⽤⼾⾝份 是否正确。
4.2 Shiro中认证的关键对象
- Subject:主体
访问系统的⽤⼾,主体可以是⽤⼾、程序等,进⾏认证的都称为主体;
- Principal:⾝份信息
是主体(subject)进⾏⾝份认证的标识,标识必须具有唯⼀性,如⽤⼾名、⼿机号、邮箱地址等,⼀ 个主体可以有多个⾝份,但是必须有⼀个主⾝份(Primary Principal)。
- credential:凭证信息
是只有主体⾃⼰知道的安全信息,如密码、证书等。
4.3 认证流程
4.4 认证的开发
1.创建项目并引用依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.5.3</version>
</dependency>
2.初次学习,在resources新增一个shiro.ini配置文件
[users]
glj=123456
3.具体例子
public class AuthenticatorTest {
public static void main(String[] args) {
// 1.创建安全管理器
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 2.给安全管理器设置realm
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
// 3.SecurityUtils给全局⼯具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
// 4.关键对象subject主体
Subject subject = SecurityUtils.getSubject();
// 5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("glj", "123456");
// 用户登录
try {
System.out.println("认证状态:"+subject.isAuthenticated());
subject.login(token);
System.out.println("认证状态:"+subject.isAuthenticated());
if (subject.isAuthenticated()) {
System.out.println("认证成...");
}
} catch (UnknownAccountException e) {
System.out.println("认证失败:用户名不存在~");
} catch (IncorrectCredentialsException e){
System.out.println("认证失败:密码错误~");
}
}
}
4.5阅读源码过程:
源码关键->⽤⼾名校验
org.apache.shiro.realm.SimpleAccountRealm ##doGetAuthenticationInfo
获取用户账号信息~
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken)token;
SimpleAccount account = this.getUser(upToken.getUsername());
if (account != null) {
if (account.isLocked()) {
throw new LockedAccountException("Account [" + account + "] is locked.");
}
if (account.isCredentialsExpired()) {
String msg = "The credentials for account [" + account + "] are expired";
throw new ExpiredCredentialsException(msg);
}
}
return account;
}
源码关键->密码校验
org.apache.shiro.realm.AuthenticatingRealm ##assertCredentialsMatch
密码的校验
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
CredentialsMatcher cm = this.getCredentialsMatcher();
if (cm != null) {
if (!cm.doCredentialsMatch(token, info)) {
String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
throw new IncorrectCredentialsException(msg);
}
} else {
throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify credentials during authentication. If you do not wish for credentials to be examined, you can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
}
}
想法:是不是把查⽤⼾名部分的实现替换成⾃⼰的,从数据库中查找呢?org.apache.shiro.realm.AuthorizingRealm
根据认证源码使用的是SimpleAccountRealm
⽬前这个SimpleAccountRealm继承了AuthorizingRealm,重写了认证和授权⽅法。
4.6 源码总结
1. AuthenticatingRealm 是认证realm, org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo
2.AuthenticatingRealm是授权realm, org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo
4.7⾃定义realm
新建自定义的realm
public class CustomerRealm extends AuthorizingRealm {
/**
* 授权
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
/**
* 认证
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取token用户名
String principal = (String) token.getPrincipal();
System.out.println("principal:"+principal);
if ("glj".equals(principal)) {
// 参数1:返回数据库账号
// 参数2:返回数据库密码
// 参数3:当前realm名称
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, "123456", this.getName());
return authenticationInfo;
}
return null;
}
}
测试例子:
public class CustomerRealmAuthenticatorTest {
public static void main(String[] args) {
// 1.创建安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
// 2.给安全管理器设置realm
defaultSecurityManager.setRealm(new CustomerRealm());
// 3.SecurityUtils给全局⼯具类设置安全管理器
SecurityUtils.setSecurityManager(defaultSecurityManager);
// 4.关键对象subject主体
Subject subject = SecurityUtils.getSubject();
// 5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("glj", "123456");
try {
subject.login(token);
} catch (IncorrectCredentialsException exception){
System.out.println("密码错误");
}
}
}
拓展的类图SimpleAuthenticationInfo
:
4.8使⽤MD5和salt
MD5方法的使用解释:
public class ShiroMD5Test {
public static void main(String[] args) {
//1.不推荐的⽤法
Md5Hash md5Hash = new Md5Hash();
md5Hash.setBytes("123456".getBytes(StandardCharsets.UTF_8));
String toHex = md5Hash.toHex();
System.out.println("toHex:"+toHex);
// 313233343536
// 使用md5
Md5Hash md5 = new Md5Hash("123456");
System.out.println("hash:"+md5.toHex());
// 使用md5+salt
Md5Hash md5Salt = new Md5Hash("123456", "glj");
System.out.println("md5_salt:"+md5Salt.toHex());
//使用mdt+salt+hash散列(需要md5 多少次)
Md5Hash md5SaltHash = new Md5Hash("123456", "glj", 1024);
System.out.println("md5_salt_hash:"+md5SaltHash.toHex());
}
}
使⽤MD5+salt+散列的例子:
public class CustomerMD5Realm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String principal = (String) authenticationToken.getPrincipal();
if ("glj".equals(principal)) {
// 参数1:返回数据库账号
// 参数2:返回数据库密码
// 参数3:注册时的随机盐
// 参数4:当前realm名称
SimpleAuthenticationInfo authenticationInfo =
new SimpleAuthenticationInfo(principal,
"cb99784c218fecedb3d592b5b37e6ba3",
ByteSource.Util.bytes("glj"),
this.getName());
return authenticationInfo;
}
return null;
}
}
package com.glj;
import com.glj.realm.CustomerMD5Realm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
/**
* @author: GuoLiangJun
* @date: Created in 2021/11/8 15:37
* Description:
* Created with IntelliJ IDEA.
*/
public class CustomerMD5RealmAuthenticatorTest {
public static void main(String[] args) {
// 1.创建安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
// 2. 给安全管理器设置realm
CustomerMD5Realm realm = new CustomerMD5Realm();
// 2.1 设置realm使用hash凭证器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
// 2.2 设置使⽤密码加密算法
credentialsMatcher.setHashAlgorithmName("md5");
// 2.3 设置散列次数(如果是md5+salt+hash1024)
credentialsMatcher.setHashIterations(1024);
realm.setCredentialsMatcher(credentialsMatcher);
defaultSecurityManager.setRealm(realm);
// 3.SecurityUtils给全局⼯具类设置安全管理器
SecurityUtils.setSecurityManager(defaultSecurityManager);
// 4.关键对象subject主体
Subject subject = SecurityUtils.getSubject();
// 5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("glj", "123456");
try {
subject.login(token);
System.out.println("登录成功");
} catch (AuthenticationException e) {
e.printStackTrace();
}
}
}
HashedCredentialsMatcher
拓展类图:
五、Shiro中的授权
5.1 授权
授权,即访问控制,控制谁能访问哪些资源。主体进⾏⾝份认证后需要分配权限⽅可访问系统的资 源,对于某些资源没有权限是⽆法访问的。
5.2 关键对象
授权可简单理解为who对what(which)进⾏How操作:
- Who,即主体(Subject),主体需要访问系统中的资源。
- What,即资源(Resource),如系统菜单、⻚⾯、按钮、类⽅法、系统商品信息等。资源包括
资源类型
和资源实例
,⽐如商品信息为资源类型,类型为t01的商品为资源实例,编号为001的商品信息也属于资源实例。 - How,权限/许可(Permission)--CRUD,规定了主体对资源的操作许可,权限离开资源没有意义,如⽤⼾
查询权限、⽤⼾添加权限、某个类⽅法的调⽤权限、编号为001⽤⼾的修改权限
等,通过权限可知主体对哪些资源都有哪些操作许可。
5.3 授权流程
5.4 授权方式
基于角色的访问控制
- RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制
if(subject.hasRole("admin")){
//admin操作哪些资源
}
基于资源的访问控制
- RBAC基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制
if (subject.isPermission("user:find:*")) {//此用户对user模块下所有资源有查找权限
// 模块.权限.资源
user:update:01 (资源实例)对01号⽤⼾具有更新权限
user:*:01 01号⽤⼾具有权限
}
if (subject.isPermission("user:update:01")) {
// (资源实例)对01号⽤⼾具有修改权限
}
if (subject.isPermission("user:update:*")) {
// (资源类型)对01号⽤⼾具有修改权限
}
5.5 权限字符串
权限字符串的规则是:资源标识符:操作:资源实例标识符,意思是对哪个资源的哪个实例具有什么操作,“:”是资源/操作/实例的分割符,权限字符串也可以使用*通配符。
例子:
- 用户创建权限:user:create,或user:create:*
- 用户修改实例001的权限:user:update:001
- 用户实例001的所有权限:user:*:001
5.6 Shiro中授权编程实现方式
- 编程式
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有权限
} else {
//无权限
}
- 注解式
@RequiresRoles("admin")
public String helloShiro() {
//有权限
}
- 标签式(Jsp页面使用的..)
<shiro:hasRole name="admin">
<!— 有权限—>
</shiro:hasRole>
完整例子():
CustomerMD5Realm
public class CustomerMD5Realm extends AuthorizingRealm {
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
System.out.println("---->"+primaryPrincipal+",又来请求拉<----");
if ("glj".equals(primaryPrincipal)) {
HashSet<String> set = new HashSet<>();
set.add("admin");
set.add("users");
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(set);
simpleAuthorizationInfo.addRole("super");
simpleAuthorizationInfo.addStringPermission("user:*:*");
return simpleAuthorizationInfo;
}
return null;
}
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String principal = (String) authenticationToken.getPrincipal();
if ("glj".equals(principal)) {
// 参数1:返回数据库账号
// 参数2:返回数据库密码
// 参数3:注册时的随机盐
// 参数4:当前realm名称
SimpleAuthenticationInfo authenticationInfo =
new SimpleAuthenticationInfo(principal,
"cb99784c218fecedb3d592b5b37e6ba3",
ByteSource.Util.bytes("glj"),
this.getName());
return authenticationInfo;
}
return null;
}
}
CustomerMD5RealmAuthenticatorTest
public class CustomerMD5RealmAuthenticatorTest {
public static void main(String[] args) {
// 1.创建安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
// 2. 给安全管理器设置realm
CustomerMD5Realm realm = new CustomerMD5Realm();
// 2.1 设置realm使用hash凭证器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
// 2.2 设置使⽤密码加密算法
credentialsMatcher.setHashAlgorithmName("md5");
// 2.3 设置散列次数(如果是md5+salt+hash1024)
credentialsMatcher.setHashIterations(1024);
realm.setCredentialsMatcher(credentialsMatcher);
defaultSecurityManager.setRealm(realm);
// 3.SecurityUtils给全局⼯具类设置安全管理器
SecurityUtils.setSecurityManager(defaultSecurityManager);
// 4.关键对象subject主体
Subject subject = SecurityUtils.getSubject();
// 5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("glj", "123456");
try {
subject.login(token);
System.out.println("登录成功");
} catch (AuthenticationException e) {
e.printStackTrace();
}
if (subject.isAuthenticated()) {
System.out.println("====== 校验是否存在此角色权限控制");
boolean hasRole = subject.hasRole("admin");
System.out.println("hasRole:"+hasRole);
System.out.println("====== 校验是否存在多个角色权限控制");
boolean hasAllRoles = subject.hasAllRoles(Arrays.asList("admin", "users"));
System.out.println("hasAllRoles:"+hasAllRoles);
System.out.println("====== 校验是否存在其中一个角色权限控制");
boolean[] hasRoles = subject.hasRoles(Arrays.asList("admin", "users", "update"));
for (boolean b : hasRoles) {
System.out.println("b:"+b);
}
System.out.println("====== 校验是否存在此资源访问权限");
// 基于权限字符串的访问控制 资源标识符:操作:资源类型
boolean subjectPermitted = subject.isPermitted("user:*:*");
System.out.println("subjectPermitted:"+subjectPermitted);
System.out.println("====== 校验是否存在多个资源访问权限");
boolean permittedAll = subject.isPermittedAll("user:*:*", "user:insert:glj");
System.out.println("permittedAll:"+permittedAll);
System.out.println("====== 校验是否存在其中一个资源访问权限");
boolean[] booleans = subject.isPermitted("user:*:*", "user:insert:glj");
for (boolean aBoolean : booleans) {
System.out.println("aBoolean"+aBoolean);
}
}
}
}
六、整合SpringBoot项⽬实战
6.1 整合思路
6.2 常⻅过滤器
6.3 简单整合之认证+简单注册的实现
6.3.1.创建项⽬
springboot+shiro
6.3.2 引⼊依赖及其application.properties配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引⼊shiro整合Springboot依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.5.3</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<!-- mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!--druia -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.16</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
spring.application.name=shiro
server.port=8081
server.servlet.context-path=/shiro
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/shiro?characterEncoding=utf-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
####mybatis-plus
mybatis-plus.mapper-locations=classpath:mapper/*.xml
mybatis-plus.type-aliases-package=com.glj.springboot_shiro.entity
6.3.3 创建配置类
@Configuration
public class ShiroConfig {
/**
* 创建shiroFilter,负责拦截所有请求
* @param defaultWebSecurityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 给filter 配置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
// 配置系统受限资源
// 配置系统公共资源
Map<String, String> map = new HashMap<>();
map.put("/**","authc");
// 设置登出
map.put("/logout", "logout");
map.put("/login","anon");
map.put("/register", "anon");
// 设置默认的登录接口...
shiroFilterFactoryBean.setLoginUrl("/loginIndex");
// 设置错误页面,认证不通过跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
/**
* 创建安全管理器
* @return
*/
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm());
return defaultWebSecurityManager;
}
/**
* 创建自定义的realm
* @return
*/
@Bean
public Realm realm(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// MD5、Salt的认证实现
hashedCredentialsMatcher.setHashAlgorithmName("md5");
hashedCredentialsMatcher.setHashIterations(1024);
CustomRealm customRealm = new CustomRealm();
customRealm.setCredentialsMatcher(hashedCredentialsMatcher);
return customRealm;
}
}
@MapperScan("com.glj.springboot_shiro.mapper")
@Configuration
public class MybatisConfig {
/**
* 防止出现没找到mapper的警告
*/
@Mapper
public interface NoWarnMapper {
}
}
6.3.4 创建自定义的realm(暂时是只有 MD5+Salt+hash的认证实现)
@Component
public class CustomRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("======doGetAuthenticationInfo=====");
if (StringUtils.isEmpty(authenticationToken.getPrincipal())) {
return null;
}
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
String username = usernamePasswordToken.getUsername();
User user = userService.findByUserName(username);
if (user !=null) {
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName());
return simpleAuthenticationInfo;
}
return null;
}
}
6.3.5 创建entity+user表
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_user")
public class User {
private String id;
private String username;
private String password;
private String salt;
}
CREATE TABLE `t_user` (
`id` varchar(40) NOT NULL,
`username` varchar(40) DEFAULT NULL,
`password` varchar(40) DEFAULT NULL,
`salt` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
6.3.6 创建Mapper接⼝
@Repository
public interface UserMapper extends BaseMapper<User> {
}
6.3.7 开发service接⼝
public interface UserService {
/**
* 注册
* @param user
*/
void register(User user);
/**
* 根据userName查找用户信息
* @param userName
* @return
*/
User findByUserName(String userName);
}
@Slf4j
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Override
public void register(User user) {
log.info("user.getPassword():"+user.getPassword());
// 处理业务 md5+salt+hash
String userId = IdUtil.simpleUUID();
user.setId(userId);
String salt = new Md5Hash(userId).toHex();
user.setSalt(salt);
Md5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024);
user.setPassword(md5Hash.toHex());
userMapper.insert(user);
}
@Override
public User findByUserName(String userName) {
QueryWrapper queryWrapper =new QueryWrapper();
queryWrapper.eq("username",userName);
User user = userMapper.selectOne(queryWrapper);
return user;
}
}
6.3.8 创建Controller
@Slf4j
@RestController
public class IndexController {
@GetMapping("/index")
public String index(){
return "this is index...";
}
@GetMapping("/error")
public String error(){
return "this is error...";
}
}
@Slf4j
@RestController
public class LoginController {
@GetMapping("/logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "logout page";
}
@GetMapping("/loginIndex")
public String loginIndex(){
return "loginIndex...";
}
@GetMapping("/login")
public String login(String username,String password){
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(username,password));
return "login success";
} catch (UnknownAccountException e) {
System.out.println("⽤⼾名错误");
} catch (IncorrectCredentialsException e){
System.out.println("密码错误");
}
return "login ...........fail";
}
}
@Slf4j
@RestController
public class RegisterController {
@Autowired
private UserService userService;
@PostMapping("/register")
public String register(User user){
try {
userService.register(user);
return "register success";
} catch (Exception e) {
return "register ...........fail";
}
}
}
6.3.9 进行接口测试
# 注册后观察数据库
127.0.0.1:8081/shiro/register?username=glj&password=glj
# 测试是否能正常登录
127.0.0.1:8081/shiro/login?username=glj&password=glj
6.4 简单整合之授权的实现
6.4.1 授权的流程表
6.4.2 数据库的建表语句+entity表
-- ----------------------------
-- Table structure for t_perms
-- ----------------------------
DROP TABLE IF EXISTS `t_perms`;
CREATE TABLE `t_perms` (
`id` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role` (
`id` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Table structure for t_role_perms
-- ----------------------------
DROP TABLE IF EXISTS `t_role_perms`;
CREATE TABLE `t_role_perms` (
`id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`roleid` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`permsid` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
`id` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`username` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`salt` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
-- ----------------------------
-- Table structure for t_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role` (
`id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`userid` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`roleid` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_user")
public class User {
private String id;
private String username;
private String password;
private String salt;
// 定义⻆⾊集合
@TableField(exist=false)
private List<Role> roles;
}
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_role")
public class Role {
private String id;
private String name;
// 定义权限的集合
@TableField(exist=false)
private List<Perms> perms;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@TableName("t_perms")
public class Perms {
private String id;
private String name;
private String url;
}
6.4.3 创建Mapper⽅法
@Repository
public interface UserMapper extends BaseMapper<User> {
/**
* 根据⽤⼾名查询所有⻆⾊
* @param username
* @return
*/
User findRolesByUserName(String username);
/**
* 根据⻆⾊id查询权限集合
* @param roleId
* @return
*/
List<Perms> findPermsByRoleId(String roleId);
}
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.glj.springboot_shiro.mapper.UserMapper">
<resultMap id="userMap" type="com.glj.springboot_shiro.entity.User">
<id column="uid" property="id"/>
<result column="username" property="username"/>
<!--⻆⾊信息-->
<collection property="roles" javaType="list" ofType="com.glj.springboot_shiro.entity.Role">
<id column="rid" property="id"/>
<result column="rname" property="name"/>
</collection>
</resultMap>
<select id="findRolesByUserName" parameterType="String" resultMap="userMap">
SELECT u.id as uid,u.username,r.id as rid,r.name as rname FROM t_user u
LEFT JOIN t_user_role ur ON u.id=ur.userid
LEFT JOIN t_role r ON ur.roleid=r.id
WHERE u.username=#{username}
</select>
<select id="findPermsByRoleId" parameterType="String" resultType="com.glj.springboot_shiro.entity.Perms">
SELECT p.id,p.name,p.url
FROM t_role r
LEFT JOIN t_role_perms rp
ON r.id=rp.roleid
LEFT JOIN t_perms p ON rp.permsid=p.id
WHERE r.id=#{id}
</select>
</mapper>
6.4.4 开发serivce:
public interface UserService {
/**
* 注册
* @param user
*/
void register(User user);
/**
* 根据userName查找用户信息
* @param userName
* @return
*/
User findByUserName(String userName);
/**
* 根据⽤⼾名查询所有⻆⾊
* @param username
* @return
*/
User findRolesByUserName(String username);
/**
* 根据⻆⾊id查询权限集合
* @param roleId
* @return
*/
List<Perms> findPermsByRoleId(String roleId);
}
@Slf4j
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper userMapper;
@Override
public void register(User user) {
log.info("user.getPassword():"+user.getPassword());
// 处理业务 md5+salt+hash
String userId = IdUtil.simpleUUID();
user.setId(userId);
String salt = new Md5Hash(userId).toHex();
user.setSalt(salt);
Md5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024);
user.setPassword(md5Hash.toHex());
userMapper.insert(user);
}
@Override
public User findByUserName(String userName) {
QueryWrapper queryWrapper =new QueryWrapper();
queryWrapper.eq("username",userName);
User user = userMapper.selectOne(queryWrapper);
return user;
}
@Override
public User findRolesByUserName(String username) {
return userMapper.findRolesByUserName(username);
}
@Override
public List<Perms> findPermsByRoleId(String roleId) {
return userMapper.findPermsByRoleId(roleId);
}
}
6.4.5 创建Controller
@Slf4j
@RestController
public class IndexController {
@RequiresPermissions("admin:*:*")
@GetMapping("/perms")
public String perms(){
return "this is perms...";
}
@RequiresRoles("admin")
@GetMapping("/admin")
public String admin(){
return "this is admin...";
}
@GetMapping("/index")
public String index(){
return "this is index...";
}
@GetMapping("/error")
public String error(){
return "this is error...";
}
}
@Slf4j
@RestController
public class LoginController {
@GetMapping("/logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "logout page";
}
@GetMapping("/loginIndex")
public String loginIndex(){
return "loginIndex...";
}
@GetMapping("/login")
public String login(String username,String password){
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(username,password));
return "login success";
} catch (UnknownAccountException e) {
System.out.println("⽤⼾名错误");
} catch (IncorrectCredentialsException e){
System.out.println("密码错误");
}
return "login ...........fail";
}
}
6.4.6 进行接口测试
# 登录
127.0.0.1:8081/shiro/login?username=glj&password=glj
# 测试admin权限
127.0.0.1:8081/shiro/admin
# 测试perms授权页面
127.0.0.1:8081/shiro/perms