Shiro自定义Token
1. 序言
- 用户登录检验需要校验多个要素,不只有用户名和密码
- 比如需要验证公司ID,主机IP,等等
2. shiro中Token的结构
Shiro中Token的继承结构图:
-
AuthenticationToken接口类只包含获取用户的身份信息和凭证信息两个方法。
-
Object getPrincipal();
-
Object getCredentials();
-
-
HostAuthenticationToken接口类继承自AuthenticationToken接口,额外添加了获取主机地址方法。
-
String getHost();
-
-
RememberMeAuthenticationToken接口类继承自AuthenticationToken接口,额外添加了是否记住用户的布尔方法
-
boolean isRememberMe();
-
!所以,若想重写Token,只需要实现AuthenticationToken接口,然后添加自己需要添加的字段就可以了。
3. 自定义Token
/**
* @Author: Hjx
* @Date: 2021/8/10 11:17
*/
public class JWTToken implements AuthenticationToken {
/**
* 公司ID
*/
String companyId;
/**
* 用户名称
*/
String userName;
/**
* 用户密码
*/
String userPass;
public JWTToken(String companyId, String userName, String userPass) {
this.companyId = companyId;
this.userName = userName;
this.userPass = userPass;
}
public void setCompanyId(String companyId) {
this.companyId = companyId;
}
public void setUserName(String userName) {
this.userName = userName;
}
public void setUserPass(String userPass) {
this.userPass = userPass;
}
public String getCompanyId() {
return companyId;
}
public String getUserName() {
return userName;
}
public String getUserPass() {
return userPass;
}
@Override
public Object getPrincipal() {
return getUserName();
}
@Override
public Object getCredentials() {
return getUserPass();
}
}
4. 重写supports方法
因为是自己定义的Token,shiro无法识别,需要修改Realm中的supports方法,使 shiro 支持自定义token。
/**
* 重写supports方法,使 Shiro 能够识别自定义的 Token
* @param token
* @return
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JWTToken;
}
5. 修改认证方法doGetAuthenticationInfo
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 从 token 中获取用户输入的身份凭证
JWTToken jwtToken = (JWTToken) token;
String userName = (String)jwtToken.getPrincipal();
// 模拟从数据库查询出的用户名和密码
String name = "hjx";
String pass = "1a0f1aac48cf23ffb080673fd60b5d2d";
// 盐值
String credentialsSalt = "hjx";
String companyId = "100000";
// 对companyId进行判断
if (!companyId.equals(jwtToken.getCompanyId())){
try {
throw new Exception("公司ID不符!!!");
} catch (Exception e) {
e.printStackTrace();
}
}
if (name.equals(userName)){
/*
SimpleAuthenticationInfo 是 AuthenticationInfo 的实现类
参数1:用户名
参数2:md5加密后的密码
参数3:盐值,类型为ByteSource,需要进行格式转换
参数4:自定义realm的名称
*/
return new SimpleAuthenticationInfo(name, pass, ByteSource.Util.bytes(credentialsSalt), this.getName());
}
return null;
}
6. 修改登录验证
/**
* @Author: Hjx
* @Date: 2021/8/10 11:43
*/
public class TestJWTAuthenticator {
public static void main(String[] args) {
// 创建SecurityManager安全管理器
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
// 给SecurityManager设置自定义Realm
JWTRealm jwtRealm = new JWTRealm();
// 设置md5 凭证适配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 设置加密方式
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 设置散列次数
hashedCredentialsMatcher.setHashIterations(1024);
jwtRealm.setCredentialsMatcher(hashedCredentialsMatcher);
defaultSecurityManager.setRealm(jwtRealm);
// 给安全工具类SecurityUtils 设置 SecurityManager
SecurityUtils.setSecurityManager(defaultSecurityManager);
// 通过安全工具类SecurityUtils获取 主体subject
Subject subject = SecurityUtils.getSubject();
// 创建Token,模拟接收前端用户输入的用户名和密码
String userName = "hjx";
String userPass = "123";
String companyId = "100000";
JWTToken jwtToken = new JWTToken(companyId,userName,userPass);
try {
System.out.println("认证状态 : "+subject.isAuthenticated());
subject.login(jwtToken);
System.out.println("认证状态 : "+subject.isAuthenticated());
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("用户名错误");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误");
}catch (Exception e){
e.printStackTrace();
}
}
}
7. 附完整的自定义Realm
/**
* @Author: Hjx
* @Date: 2021/8/10 11:25
*/
public class JWTRealm extends AuthorizingRealm {
/**
* 重写supports方法,使 Shiro 能够识别自定义的 Token
* @param token
* @return
*/
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof JWTToken;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
/*
PrincipalCollection 身份的集合
一个主体可以有多个身份,但是只有一个主身份
*/
// 获取主体的主身份
String principal = (String) principals.getPrimaryPrincipal();
System.out.println("主身份信息"+principal);
// 模拟根据用户的身份信息 从数据库中查询其角色信息
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addRole("user");
// 模拟从数据库查询权限信息,赋值给某个对象
simpleAuthorizationInfo.addStringPermission("user:*:01");
simpleAuthorizationInfo.addStringPermission("product:create:01");
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 从 token 中获取用户输入的身份凭证
JWTToken jwtToken = (JWTToken) token;
String userName = (String)jwtToken.getPrincipal();
// 模拟从数据库查询出的用户名和密码
String name = "hjx";
String pass = "1a0f1aac48cf23ffb080673fd60b5d2d";
// 盐值
String credentialsSalt = "hjx";
String companyId = "100000";
// 对companyId进行判断
if (!companyId.equals(jwtToken.getCompanyId())){
try {
throw new Exception("公司ID不符!!!");
} catch (Exception e) {
e.printStackTrace();
}
}
if (name.equals(userName)){
/*
SimpleAuthenticationInfo 是 AuthenticationInfo 的实现类
参数1:用户名
参数2:md5加密后的密码
参数3:盐值,类型为ByteSource,需要进行格式转换
参数4:自定义realm的名称
*/
return new SimpleAuthenticationInfo(name, pass, ByteSource.Util.bytes(credentialsSalt), this.getName());
}
return null;
}
}
代码及资料地址:hjx: 知道的越多,越发觉自己的无知 (gitee.com)