查看原文
其他

2021最新版Shiro安全框架快速入门

脚本之家 2022-04-23

The following article is from Java技术迷 Author 汪伟俊

 关注
脚本之家
”,与百万开发者在一起

汪伟俊 | 作者

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=123456lisi=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();}......

运行结果:

falsetrue

而且通过测试可以发现,当用户名错误时:

......//创建令牌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技术迷专栏作者 投稿,未经允许请勿转载。
突发!

  推荐阅读:

一个C#开发编写Java框架的心路历程

Java 集合框架看这一篇就够了

C、Java和Python争夺第一,TIOBE CEO看好Python

送命题,选 C++ 还是 Java?

ERP已死,“中台”已凉,“低代码”称王!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存