2021最新版Shiro安全框架快速入门
汪伟俊 | 作者
Java技术迷 | 出品
初识 Shiro
何为 Shiro?Shiro 是一个强大且易用的 Java 安全框架,执行身份验证、授权、密码和会话管理。使用 Shiro 的易于理解的 API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
啥意思呢?我们知道,任何一个系统都需要有权限控制,你不可能在没有登录的情况下能够访问到系统的内部页面,这就是权限控制的功劳,权限控制会限制每个角色的系统权限,只有当你有权限时才能够做权限对应的事情,而 Shiro 就是帮助我们实现这一过程的一个安全框架。
入门案例
在开始编写案例之前,我们先来了解几个 Shiro 中的常用对象:
•Subject(主体):指访问系统的用户或程序•Principal(身份信息):指主体进行身份认证的标识•Credential(凭证信息):主体进行认证的安全信息,比如密码、证书等
下面来编写一个入门案例来感受一下 Shiro 框架的妙用,首先引用依赖:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.5.3</version>
</dependency>
然后编写 Shiro 的配置文件,Shiro 对配置文件名没有太多的要求,只需要后缀名为 .ini 即可:
[users]
zhangsan=123456
lisi=654321
事实上当我们将 Shiro 整合到其它框架诸如 Spring Boot,是不需要这个配置文件的,Shiro 提供给我们这个配置文件是方便开发者进行学习的,通过该配置文件可以配置一些固定的数据,使得开发者无需连接数据库即可进行权限认证的开发。
下面编写认证代码:
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;
public class AuthenticatorTest {
public static void main(String[] args) {
//创建安全管理器
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//设置 Realms
securityManager.setRealm(new IniRealm("classpath:shiro-config.ini"));
//借助安全工具类实现认证
SecurityUtils.setSecurityManager(securityManager);
//获取主体
Subject subject = SecurityUtils.getSubject();
//创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","123456");
try {
//用户认证
subject.login(token);
}catch (Exception e){
e.printStackTrace();
}
}
}
首先创建安全管理器,并设置 Realms,Realms 设置的是认证数据,后期整合的话 Realms 的数据肯定是要从数据库中读取的,然后借助安全工具类进行认证,并获取主体,创建安全令牌,最后执行 login 方法;Shiro 就会去 Relams 指定的数据中匹配与令牌匹配的用户信息,若匹配成功,则认证成功;否则,认证失败。
这里因为 Relams 数据中有 zhangsan 和 123456 的账户,所以会认证成功,但是 login 方法是没有返回值的,所以我们通过 try...catch
抓取异常,当认证成功时控制台不会有任何输出;而当认证失败时,控制台会抛出异常:
org.apache.shiro.authc.UnknownAccountException: Realm [org.apache.shiro.realm.text.IniRealm@32a1bec0] was unable to find account data for the submitted AuthenticationToken [org.apache.shiro.authc.UsernamePasswordToken - zhanan, rememberMe=false].
at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doSingleRealmAuthentication(ModularRealmAuthenticator.java:184)
at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doAuthenticate(ModularRealmAuthenticator.java:273)
at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:198)
at org.apache.shiro.mgt.AuthenticatingSecurityManager.authenticate(AuthenticatingSecurityManager.java:106)
at org.apache.shiro.mgt.DefaultSecurityManager.login(DefaultSecurityManager.java:275)
at org.apache.shiro.subject.support.DelegatingSubject.login(DelegatingSubject.java:260)
at com.wwj.AuthenticatorTest.main(AuthenticatorTest.java:24)
当然,你其实也可以通过 subject.isAuthenticated() 方法获取认证状态,比如:
......
try {
//用户认证
System.out.println(subject.isAuthenticated());
subject.login(token);
System.out.println(subject.isAuthenticated());
}catch (Exception e){
e.printStackTrace();
}
......
运行结果:
false
true
而且通过测试可以发现,当用户名错误时:
......
//创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("zhang","123456");
try {
//用户认证
subject.login(token);
}catch (Exception e){
e.printStackTrace();
}
会抛出如下异常:
org.apache.shiro.authc.UnknownAccountException: Realm [org.apache.shiro.realm.text.IniRealm@722c41f4] was unable to find account data for the submitted AuthenticationToken [org.apache.shiro.authc.UsernamePasswordToken - zhang, rememberMe=false].
......
而当密码错误时:
//创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","111");
try {
//用户认证
subject.login(token);
}catch (Exception e){
e.printStackTrace();
}
会抛出如下异常:
org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - zhangsan, rememberMe=false] did not match the expected credentials.
......
所以我们可以改进一下这个程序:
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;
public class AuthenticatorTest {
public static void main(String[] args) {
//创建安全管理器
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//设置 Realms
securityManager.setRealm(new IniRealm("classpath:shiro-config.ini"));
//借助安全工具类实现认证
SecurityUtils.setSecurityManager(securityManager);
//获取主体
Subject subject = SecurityUtils.getSubject();
//创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("zhang","123456");
try {
//用户认证
subject.login(token);
}catch (UnknownAccountException e){
System.out.println("用户名错误!");
}catch (IncorrectCredentialsException e){
System.out.println("密码错误!");
}
}
}
运行结果:
用户名错误!
源码分析
下面来看看 Shiro 究竟是如何实现权限的控制的。
在 login 方法处打一个断点,调试运行:
可以看到虽然外部我们是用的 Subject 主体对象调用的 login 方法啊,但内部其实用的还是 SecurityManager 对象调用 login,SecurityManager 就是 Shiro 的核心架构。
我们进入 SecurityManager 类的 login 方法看看它做了什么:
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException{
AuthenticationInfo info;
try {
//注意这个方法
info = this.authenticate(token);
} catch (AuthenticationException var7) {
AuthenticationException ae = var7;
try {
this.onFailedLogin(token, ae, subject);
} catch (Exception var6) {
if (log.isInfoEnabled()) {
log.info("onFailedLogin method threw an exception. Logging and propagating original AuthenticationException.", var6);
}
}
throw var7;
}
Subject loggedIn = this.createSubject(token, info, subject);
this.onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
可以看到该方法再最开始调用了一个 authenticate 方法,并将 token 令牌作为参数传递进去,所以我们进去看看这个方法:
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
return this.authenticator.authenticate(token);
}
该方法又继续调用了一个方法,同样地,我们查看该 authenticate 方法的源码:
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
if (token == null) {
throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
} else {
log.trace("Authentication attempt received for token [{}]", token);
AuthenticationInfo info;
try {
//注意这个方法
info = this.doAuthenticate(token);
if (info == null) {
String msg = "No account information found for authentication token [" + token + "] by this Authenticator instance. Please check that it is configured correctly.";
throw new AuthenticationException(msg);
}
} catch (Throwable var8) {
AuthenticationException ae = null;
if (var8 instanceof AuthenticationException) {
ae = (AuthenticationException)var8;
}
if (ae == null) {
String msg = "Authentication failed for token submission [" + token + "]. Possible unexpected error? (Typical or expected login exceptions should extend from AuthenticationException).";
ae = new AuthenticationException(msg, var8);
if (log.isWarnEnabled()) {
log.warn(msg, var8);
}
}
try {
this.notifyFailure(token, ae);
} catch (Throwable var7) {
if (log.isWarnEnabled()) {
String msg = "Unable to send notification for failed authentication attempt - listener error?. Please check your AuthenticationListener implementation(s). Logging sending exception and propagating original AuthenticationException instead...";
log.warn(msg, var7);
}
}
throw ae;
}
log.debug("Authentication successful for token [{}]. Returned account [{}]", token, info);
this.notifySuccess(token, info);
return info;
}
}
该方法除了抓取一些异常外,最关键的代码还是调用了一个 doAuthenticate 方法,继续查看该方法的源码:
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
this.assertRealmsConfigured();
Collection<Realm> realms = this.getRealms();
return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);
}
该方法会去判断项目中是否配置了 Realms,这里我们当然是配置了的,且只配置了一个文件,所以它会执行 doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken)
方法,查看该方法:
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
if (!realm.supports(token)) {
String msg = "Realm [" + realm + "] does not support authentication token [" + token + "]. Please ensure that the appropriate Realm implementation is configured correctly or that the realm accepts AuthenticationTokens of this type.";
throw new UnsupportedTokenException(msg);
} else {
//注意这个方法
AuthenticationInfo info = realm.getAuthenticationInfo(token);
if (info == null) {
String msg = "Realm [" + realm + "] was unable to find account data for the submitted AuthenticationToken [" + token + "].";
throw new UnknownAccountException(msg);
} else {
return info;
}
}
}
该方法继续调用 getAuthenticationInfo 方法,查看该方法:
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = this.getCachedAuthenticationInfo(token);
if (info == null) {
//注意这个方法
info = this.doGetAuthenticationInfo(token);
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
this.cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}
if (info != null) {
//注意这个方法
this.assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}
return info;
}
这里首先获取缓存中的认证信息,因为我们没有配置缓存管理器,所以 info 肯定为 null,程序会执行第一个 if 语句块,也就是执行 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;
}
该方法将我们传递过去的 token 强转为 UsernamePasswordToken 对象,并调用了 getUser 方法:
protected SimpleAccount getUser(String username) {
this.USERS_LOCK.readLock().lock();
SimpleAccount var2;
try {
var2 = (SimpleAccount)this.users.get(username);
} finally {
this.USERS_LOCK.readLock().unlock();
}
return var2;
}
该方法会利用 username 封装一个 SimpleAccount 对象,因为这里的依据只是 username,所以 doGetAuthenticationInfo 方法只是对用户名的一个认证,之后,程序调用 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.");
}
}
看到这段异常应该很熟悉吧,没错,这就是一开始密码错误时运行抛出的异常。
以上就是入门案例程序的权限认证流程。
自定义 Realm
入门案例中使用的 .ini 配置文件的方式生成 Realm,而在后续的整合需求中,我们的 Realm 数据往往都是从数据库中读取的,为此,我们需要自定义实现 Realm。通过刚才的源码分析,我们也能够知道,若是想要自定义 Realm,则需要继承 AuthorizingRealm:
public class SimpleAccountRealm extends AuthorizingRealm {
......
}
下面创建一个类并继承该类:
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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
public class MyRealm extends AuthorizingRealm {
/**
* 授权方法
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 认证方法
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//从 token 中获取用户名
String principal = (String) authenticationToken.getPrincipal();
//若是用户名为 zhangsan,则认证成功
if("zhangsan".equals(principal)){
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo("zhangsan","123456",this.getName());
return info;
}
return null;
}
}
需要实现两个方法,分别用于授权和认证,授权我们先跳过,直接实现一下认证,从 Token 中获取前面传递过来的用户名,并与真实用户名进行校验,这一环节一般需要从数据库中查询是否有与之匹配的用户名,若没有则认证失败,这里为了方便演示,就直接用死数据了。
若是匹配成功,则认证就成功了,此时需要返回一个 SimpleAuthenticationInfo 对象,并封装一些信息,下面介绍一下该对象的构造方法参数:
•参数 1:数据库中正确的用户名•参数 2:数据库中正确的密码•参数 3:提供当前 Realm 的名字,可直接调用 this.getName() 获取
下面测试一下:
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
public class MyRealmTest {
public static void main(String[] args) {
//创建 SecurityManager
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//设置自定义的 Realm
securityManager.setRealm(new MyRealm());
//借助安全工具类实现认证
SecurityUtils.setSecurityManager(securityManager);
//获取主体
Subject subject = SecurityUtils.getSubject();
//创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","123456");
try {
//认证
subject.login(token);
}catch (Exception e){
e.printStackTrace();
}
}
}
密码正确时,控制台无输出,密码错误时,控制台抛出异常:
org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - zhangsan, rememberMe=false] did not match the expected credentials.
MD5 加密
关于 MD5 加密,这里不作具体讲解,只需要知道 MD5 是用来加密的,其生成结果是一个 16 进制的 32 位长度字符串,且该加密算法是不可逆的。
Shiro 中提供了非常简单的 API 用于实现 MD5 加密,比如:
import org.apache.shiro.crypto.hash.Md5Hash;
public class MD5Test {
public static void main(String[] args) {
String password = "123456";
//创建 Md5Hash 对象
Md5Hash md5Hash = new Md5Hash(password);
String str = md5Hash.toHex();
System.out.println(str);
}
}
运行结果:
e10adc3949ba59abbe56e057f20f883e
而有时候这种加密并不是绝对安全的,如果你百度搜索一下,你可以找到很多 MD5 解密的网站:
我们将运行后得到的密文在网站中进行解密,发现密码竟然被查询出来了,而前面不是说 MD5 是不可逆的吗?
MD5 确实是不可逆的,但这些网站为什么能够查询出来呢?其实它只是用的穷举法,这些网站会将一些常见的比较简单的密码密文保存到数据库,当你用一个密文去解密的时候,网站就会用你提供的密文去撞库,一旦碰撞成功,密码就被查询出来了,所以只要你的密码足够复杂,就不可能解密出来,至少现在是这样的。
所以,为了让 MD5 加密后的密文更加安全,我们需要对用户设置的密码进行加盐操作,使得用户即使在设置了诸如 123456 这样简单的密码时,解密工具也无法破解出来:
import org.apache.shiro.crypto.hash.Md5Hash;
public class MD5Test {
public static void main(String[] args) {
String password = "123456";
//给密码加盐
String salt = "ls.g*y";
//创建 Md5Hash 对象
Md5Hash md5Hash = new Md5Hash(password,salt);
String str = md5Hash.toHex();
System.out.println(str);
}
}
运行结果:
08be5e130b57ba0dac206e6e900f76f4
现在拿着这段密文去解密网站试试:
显然它已经“无能为力”了。
掌握了如何通过 Md5Hash 对密码进行加密后,我们可以将其运用到注册中,当用户注册一个账户后,对密码进行加密再存入数据库;然而此时新的问题产生了,当密码被加密后,现在 Shiro 的权限认证将永远无法通过,因为你获取的是用户输入的密码,但数据库中存放的显然已经不是这样一段明文了,为此,我们需要修改一下认证的代码,让其也对用户登录时输入的密码进行 MD5 加密并加盐,然后与数据库中的密文进行比较,若匹配,才认证成功。
接下来实现一下,同样编写一个类继承 AuthorizingRealm:
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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
public class MyMD5Realm extends AuthorizingRealm {
/**
* 授权方法
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 认证方法
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获取 username
String username = (String) authenticationToken.getPrincipal();
//模拟查询数据库
if("zhangsan".equals(username)){
return new SimpleAuthenticationInfo(username,"e10adc3949ba59abbe56e057f20f883e",this.getName());
}
return null;
}
}
这里模拟从数据中获取了一段密文,那么在认证的时候我们就需要将用户输入的密码也进行 MD5 加密:
public static void main(String[] args) {
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//创建 Realm
MyMD5Realm realm = new MyMD5Realm();
//设置 Realm 使用 hash 匹配器
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("md5");
realm.setCredentialsMatcher(matcher);
securityManager.setRealm(realm);
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123456");
try {
subject.login(token);
System.out.println("认证成功!");
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("用户名错误!");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误!");
}
}
运行结果:
认证成功!
还可以对其加盐处理:
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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
public class MyMD5Realm extends AuthorizingRealm {
/**
* 授权方法
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 认证方法
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获取 username
String username = (String) authenticationToken.getPrincipal();
//模拟查询数据库
if("zhangsan".equals(username)){
//盐值
String salt = "ls.g*y";
//加盐后的密文 08be5e130b57ba0dac206e6e900f76f4
return new SimpleAuthenticationInfo(username,"08be5e130b57ba0dac206e6e900f76f4", ByteSource.Util.bytes(salt),this.getName());
}
return null;
}
}
为了使前端页面能够匹配密文,我们需要将盐值作为参数进行传递,使用 ByteSource.Util.bytes() 方法,然后直接运行测试代码,得到结果:
认证成功!
需要注意的是,只要你将盐值作为参数传递进去了,Shiro 将会自动地对前端传递过来的密码加盐处理,然后与数据库查询得到的密文进行比较。
授权
授权指的是对认证后的用户授予对应的权限,对于没有权限的一些资源,比如页面,用户将无法访问。
授权一般分为两种:
•基于角色的访问控制•基于资源的访问控制
而这其中都是通过权限字符串去限定权限范围的,权限字符串的规则是 资源标识符:操作:资源实例标识符
。比如:
•user:create
:用户的创建权限•user:update:test
:用户更新 test 资源的权限
下面在原程序的基础上进行授权的开发:
public class MyMD5Realm extends AuthorizingRealm {
/**
* 授权方法
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取身份信息
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
//获取当前用户的角色信息、权限信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//模拟查询数据库,添加用户的角色信息
info.addRole("admin");
info.addRole("user");
//现在登录用户拥有管理员和普通用户的双重角色
return info;
}
/**
* 认证方法
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获取 username
String username = (String) authenticationToken.getPrincipal();
//模拟查询数据库
if("zhangsan".equals(username)){
//盐值
String salt = "ls.g*y";
//加盐后的密文 08be5e130b57ba0dac206e6e900f76f4
return new SimpleAuthenticationInfo(username,"08be5e130b57ba0dac206e6e900f76f4", ByteSource.Util.bytes(salt),this.getName());
}
return null;
}
}
编写测试代码:
public static void main(String[] args) {
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//创建 Realm
MyMD5Realm realm = new MyMD5Realm();
//设置 Realm 使用 hash 匹配器
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("md5");
realm.setCredentialsMatcher(matcher);
securityManager.setRealm(realm);
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123456");
try {
subject.login(token);
System.out.println("认证成功!");
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("用户名错误!");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误!");
}
//判断用户是否认证通过
if(subject.isAuthenticated()){
//认证用户进行授权
//基于角色的权限控制
boolean flag = subject.hasRole("admin");
System.out.println(flag);
}
}
此时登录的用户 zhangsan 具有 admin 和 user 角色,所以运行结果应该为 true:
认证成功!
true
还有基于多角色的权限控制:
//判断用户是否认证通过
if(subject.isAuthenticated()){
//认证用户进行授权
//基于多角色的权限控制
boolean flag = subject.hasAllRoles(Arrays.asList("admin", "user"));
System.out.println(flag);
}
此时用户需同时满足这两个角色信息。
对于管理员角色,它是系统的最高权限,所以只要用户具有 admin 权限,它就能够去操作系统中的所有资源:
//判断用户是否认证通过
if(subject.isAuthenticated()){
boolean[] flags = subject.hasRoles(Arrays.asList("admin", "user"));
System.out.println(flags);
}
该方法的返回值是一个数组,也就是说,如果用户具有两个角色,则数组值均为 true。
还有诸如权限字符串的权限控制:
//基于权限字符串的权限控制
boolean permitted = subject.isPermitted("user:*:*");
本文作者:汪伟俊 为Java技术迷专栏作者 投稿,未经允许请勿转载。