shiro源码分析(四)具体的Realm

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介:
首先还是Realm的接口设计图:  

07101951_ijBU.png  
这里只来说明SimpleAccountRealm和JdbcRealm的实现。 
首先是SimpleAccountRealm不用关心数据的具体来源,只提供了与上层的交互,即实现了AuthenticatingRealm遗留的AuthenticationInfo doGetAuthenticationInfo和AuthorizingRealm遗留的AuthorizationInfo doGetAuthorizationInfo。 
如下:
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected final Map<String, SimpleAccount> users; //username-to-SimpleAccount
     protected final Map<String, SimpleRole> roles; //roleName-to-SimpleRole
     protected final ReadWriteLock USERS_LOCK;
     protected final ReadWriteLock ROLES_LOCK;
 
     public SimpleAccountRealm() {
         this .users = new LinkedHashMap<String, SimpleAccount>();
         this .roles = new LinkedHashMap<String, SimpleRole>();
         USERS_LOCK = new ReentrantReadWriteLock();
         ROLES_LOCK = new ReentrantReadWriteLock();
         //SimpleAccountRealms are memory-only realms - no need for an additional cache mechanism since we're
         //already as memory-efficient as we can be:
         setCachingEnabled( false );
     }

SimpleAccountRealm内部有四个属性,Map<String, SimpleAccount> users:用于存放用户账号信息,Map<String, SimpleRole> roles用于存放角色名的信息。这两个都是各种配置的最终归属存储地。 
ReadWriteLock USERS_LOCK:由于这些配置信息,一般不会去修改,大部分时间用于查询,所以要使用读写锁。一般的synchronized同步,不管你是读还是写,都要进行等待。写与写需要进行同步,写与读也要进行同步,但读与读却并不需要进行同步,所以对于那些经常读的场景,要使用读写锁ReadWriteLock 来提升性能。ReadWriteLock ROLES_LOCK同理。 
有了以上数据源,实现父类的遗留的方法就比较简单了。如下:
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
         UsernamePasswordToken upToken = (UsernamePasswordToken) token;
         SimpleAccount account = 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;
     }
 
     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
         String username = getUsername(principals);
         USERS_LOCK.readLock().lock();
         try {
             return this .users.get(username);
         } finally {
             USERS_LOCK.readLock().unlock();
         }
     }

代码就很简单了,就是从users中取出相应的用户数据。接下来要分析清几个概念: 
AuthorizationInfo、AuthenticationInfo、SimpleAccount、SimpleRole、PrincipalCollection。 
PrincipalCollection:看下文档介绍
 
引用

/** 
* A collection of all principals associated with a corresponding {  @link Subject Subject}.  A <em>principal</em> is 
* just a security term for an identifying attribute, such as a username or user id or social security number or 
* anything else that can be considered an 'identifying' attribute for a {  @code Subject}. 
* <p/> 
* A PrincipalCollection organizes its internal principals based on the {  @code Realm} where they came from when the 
* Subject was first created.  To obtain the principal(s) for a specific Realm, see the {  @link #fromRealm} method.  You 
* can also see which realms contributed to this collection via the {  @link #getRealmNames() getRealmNames()} method. 
*/ 

一个principal仅仅是Subject的一个标识而已,如可以是用户名,用户id等。PrincipalCollection则是这些属性的集合。每个用户属性可以来自不同的Realm。Collection fromRealm(String realmName)可以获取某个Realm的所有用户属性。Set<String> getRealmNames()可以获取到与Subject关联的用户的属性来自于哪些Realm。 
Object getPrimaryPrincipal():主要是用于获取唯一标示,如UUID、username等。 
接口如下:
 

07101951_BAST.png  
MutablePrincipalCollection如下:  
?
1
2
3
4
5
6
public interface MutablePrincipalCollection extends PrincipalCollection {
     void add(Object principal, String realmName);
     void addAll(Collection principals, String realmName);
     void addAll(PrincipalCollection principals);
     void clear();
}

我们知道每一个标示都有所属的realm,所以再添加的时候,要带上realmName。 
SimplePrincipalCollection:
 
?
1
private Map<String, Set> realmPrincipals;

一个重要的数据集合,key是realm的name,value是principal集合。 
这个接口分支一直在强调,每个principal都是有所属的realm的。 
PrincipalMap:我这一块没有搞明白,先放下。 

AuthenticationInfo 它是含有用户和密码信息的地方:
 
?
1
2
3
4
public interface AuthenticationInfo extends Serializable {
     PrincipalCollection getPrincipals();
     Object getCredentials();
}

AuthorizationInfo :存放用户权限的地方  
?
1
2
3
4
5
public interface AuthorizationInfo extends Serializable {
     Collection<String> getRoles();
     Collection<String> getStringPermissions();
     Collection<Permission> getObjectPermissions();
}

类图如下:  
07101951_OESJ.png  
MergableAuthenticationInfo 意味着AuthenticationInfo可以进行合并:  
?
1
2
3
public interface MergableAuthenticationInfo extends AuthenticationInfo {
     void merge(AuthenticationInfo info);
}

SaltedAuthenticationInfo 主要用于密码匹配,后续文章专门说明:  
?
1
2
3
public interface SaltedAuthenticationInfo extends AuthenticationInfo {
     ByteSource getCredentialsSalt();
}

SimpleAuthenticationInfo:存储了三个重要的属性:  
?
1
2
3
protected PrincipalCollection principals;
     protected Object credentials;
     protected ByteSource credentialsSalt;

然后就是实现了MergableAuthenticationInfo 接口,可以进行合并,这里的合并在第一篇文章中Realm认证中提到过:  
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public void merge(AuthenticationInfo info) {
         if (info == null || info.getPrincipals() == null || info.getPrincipals().isEmpty()) {
             return ;
         }
 
         if ( this .principals == null ) {
             this .principals = info.getPrincipals();
         } else {
             if (!( this .principals instanceof MutablePrincipalCollection)) {
                 this .principals = new SimplePrincipalCollection( this .principals);
             }
             ((MutablePrincipalCollection) this .principals).addAll(info.getPrincipals());
         }
 
         //only mess with a salt value if we don't have one yet.  It doesn't make sense
         //to merge salt values from different realms because a salt is used only within
         //the realm's credential matching process.  But if the current instance's salt
         //is null, then it can't hurt to pull in a non-null value if one exists.
         //
         //since 1.1:
         if ( this .credentialsSalt == null && info instanceof SaltedAuthenticationInfo) {
             this .credentialsSalt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();
         }
 
         Object thisCredentials = getCredentials();
         Object otherCredentials = info.getCredentials();
 
         if (otherCredentials == null ) {
             return ;
         }
 
         if (thisCredentials == null ) {
             this .credentials = otherCredentials;
             return ;
         }
 
         if (!(thisCredentials instanceof Collection)) {
             Set newSet = new HashSet();
             newSet.add(thisCredentials);
             setCredentials(newSet);
         }
 
         // At this point, the credentials should be a collection
         Collection credentialCollection = (Collection) getCredentials();
         if (otherCredentials instanceof Collection) {
             credentialCollection.addAll((Collection) otherCredentials);
         } else {
             credentialCollection.add(otherCredentials);
         }
     }

主要分principals、credentialsSalt 和credentials三项的合并,代码也和简单。 
SimpleAuthorizationInfo:存放了认证用户的角色和用户权限。
 
?
1
2
3
protected Set<String> roles;
protected Set<String> stringPermissions;
protected Set<Permission> objectPermissions;

最重要的就是SimpleAccount:  
?
1
2
3
4
5
6
public class SimpleAccount implements Account, MergableAuthenticationInfo, SaltedAuthenticationInfo, Serializable {
     private SimpleAuthenticationInfo authcInfo;
     private SimpleAuthorizationInfo authzInfo;
     private boolean locked;
     private boolean credentialsExpired;
}

它有SimpleAuthenticationInfo 、SimpleAuthorizationInfo ,所以是用户认证信息和权限信息的汇总。 
还有两个属性locked和credentialsExpired,用来表示用户的锁定和密码过期的状态。 
至此整个SimpleAccount便介绍完了。 

回到SimpleAccountRealm,SimpleAccountRealm已经拥有Map<String, SimpleAccount> users和Map<String, SimpleRole> roles数据了,但是这些数据是怎么产生的呢?这就需要交给它的子类TextConfigurationRealm来完成:
 
?
1
2
private volatile String userDefinitions;
private volatile String roleDefinitions;

仅仅两个字符串包含了所有的用户和角色的配置总来源。所以TextConfigurationRealm主要就是对这两个字符串的解析:  
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Override
protected void onInit() {
         super .onInit();
         processDefinitions();
     }
protected void processDefinitions() {
         try {
           //解析角色配置
             processRoleDefinitions();
          //解析用户配置
             processUserDefinitions();
         } catch (ParseException e) {
             String msg = "Unable to parse user and/or role definitions." ;
             throw new ConfigurationException(msg, e);
         }
     }
protected void processRoleDefinitions() throws ParseException {
         String roleDefinitions = getRoleDefinitions();
         if (roleDefinitions == null ) {
             return ;
         }
         //先将角色字符串按行分割,然后每行再按照key value分割
         Map<String, String> roleDefs = toMap(toLines(roleDefinitions));
         processRoleDefinitions(roleDefs);
     }
protected void processRoleDefinitions(Map<String, String> roleDefs) {
         if (roleDefs == null || roleDefs.isEmpty()) {
             return ;
         }
         for (String rolename : roleDefs.keySet()) {
             String value = roleDefs.get(rolename);
 
             SimpleRole role = getRole(rolename);
             if (role == null ) {
                 role = new SimpleRole(rolename);
                 add(role);
             }
 
             Set<Permission> permissions = PermissionUtils.resolveDelimitedPermissions(value, getPermissionResolver());
             role.setPermissions(permissions);
         }
     }

再通过PermissionResolver将字符串形式的权限转化成Permission对象,知道大致情况了,就可以了,不需要每一步都弄清楚。 
TextConfigurationRealm主要用于解析两个配置字符串,这两个配置字符串的产生则继续交给子类来完成。IniRealm则是通过ini配置文件来产生这两个字符串,PropertiesRealm则是通过properties文件来产生这两个字符串。 

至此,SimpleAccountRealm这一路就大致走通了,接下来就是另一条路JdbcRealm了。
 
?
1
2
3
4
5
6
7
public class JdbcRealm extends AuthorizingRealm {
     protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password from users where username = ?" ;
     protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = "select password, password_salt from users where username = ?" ;
     protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?" ;
     protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?" ;
     protected DataSource dataSource;
}

首先是含有这几个默认的sql和DataSource dataSource,用于从数据库中获取相应的用户、角色、权限等数据。 
根据上一篇文章我们知道JdbcRealm 要实现AuthenticatingRealm遗留的AuthenticationInfo doGetAuthenticationInfo和AuthorizingRealm遗留的AuthorizationInfo doGetAuthorizationInfo。下面就看看是怎么来实现的:
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
 
         UsernamePasswordToken upToken = (UsernamePasswordToken) token;
         String username = upToken.getUsername();
 
         // Null username is invalid
         if (username == null ) {
             throw new AccountException( "Null usernames are not allowed by this realm." );
         }
 
         Connection conn = null ;
         SimpleAuthenticationInfo info = null ;
         try {
             conn = dataSource.getConnection();
 
             String password = null ;
             String salt = null ;
             switch (saltStyle) {
             case NO_SALT:
                 //根据用户名去查找密码
                 password = getPasswordForUser(conn, username)[ 0 ];
                 break ;
             case CRYPT:
                 // TODO: separate password and hash from getPasswordForUser[0]
                 throw new ConfigurationException( "Not implemented yet" );
                 //break;
             case COLUMN:
                 String[] queryResults = getPasswordForUser(conn, username);
                 password = queryResults[ 0 ];
                 salt = queryResults[ 1 ];
                 break ;
             case EXTERNAL:
                 password = getPasswordForUser(conn, username)[ 0 ];
                  //此时salt不存在数据库中,默认的值为username
                 salt = getSaltForUser(username);
             }
 
             if (password == null ) {
                 throw new UnknownAccountException( "No account found for user [" + username + "]" );
             }
             //根据用户名、密码、盐值构建一个SimpleAuthenticationInfo
             info = new SimpleAuthenticationInfo(username, password.toCharArray(), getName());
             
             if (salt != null ) {
                 info.setCredentialsSalt(ByteSource.Util.bytes(salt));
             }
 
         } catch (SQLException e) {
             final String message = "There was a SQL error while authenticating user [" + username + "]" ;
             if (log.isErrorEnabled()) {
                 log.error(message, e);
             }
 
             // Rethrow any SQL errors as an authentication exception
             throw new AuthenticationException(message, e);
         } finally {
             JdbcUtils.closeConnection(conn);
         }
 
         return info;
     }
private String[] getPasswordForUser(Connection conn, String username) throws SQLException {
 
         String[] result;
         boolean returningSeparatedSalt = false ;
         switch (saltStyle) {
         case NO_SALT:
         case CRYPT:
         case EXTERNAL:
             result = new String[ 1 ];
             break ;
         default :
             result = new String[ 2 ];
             returningSeparatedSalt = true ;
         }
         
         PreparedStatement ps = null ;
         ResultSet rs = null ;
         try {
             ps = conn.prepareStatement(authenticationQuery);
             ps.setString( 1 , username);
 
             // Execute query
             rs = ps.executeQuery();
 
             // Loop over results - although we are only expecting one result, since usernames should be unique
             boolean foundResult = false ;
             while (rs.next()) {
 
                 // Check to ensure only one row is processed
                 if (foundResult) {
                     throw new AuthenticationException( "More than one user row found for user [" + username + "]. Usernames must be unique." );
                 }
 
                 result[ 0 ] = rs.getString( 1 );
                 if (returningSeparatedSalt) {
                     result[ 1 ] = rs.getString( 2 );
                 }
 
                 foundResult = true ;
             }
         } finally {
             JdbcUtils.closeResultSet(rs);
             JdbcUtils.closeStatement(ps);
         }
 
         return result;
     }
protected String getSaltForUser(String username) {
         return username;
     }

代码很简单就不再一一细说。再看下doGetAuthorizationInfo是怎么实现的:  
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
 
         //null usernames are invalid
         if (principals == null ) {
             throw new AuthorizationException( "PrincipalCollection method argument cannot be null." );
         }
 
         String username = (String) getAvailablePrincipal(principals);
 
         Connection conn = null ;
         Set<String> roleNames = null ;
         Set<String> permissions = null ;
         try {
             conn = dataSource.getConnection();
 
             // Retrieve roles and permissions from database
             roleNames = getRoleNamesForUser(conn, username);
             if (permissionsLookupEnabled) {
                 permissions = getPermissions(conn, username, roleNames);
             }
 
         } catch (SQLException e) {
             final String message = "There was a SQL error while authorizing user [" + username + "]" ;
             if (log.isErrorEnabled()) {
                 log.error(message, e);
             }
 
             // Rethrow any SQL errors as an authorization exception
             throw new AuthorizationException(message, e);
         } finally {
             JdbcUtils.closeConnection(conn);
         }
 
         SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);
         info.setStringPermissions(permissions);
         return info;
 
     }

第一步先根据PrincipalCollection 来获取用户名,第二步根据用户名来获取角色,第三部根据角色和用户名来获取权限。后两步都是执行简单的sql,不再说,看下如何由PrincipalCollection 获取用户名,该方法定义在CachingRealm中:  
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
protected Object getAvailablePrincipal(PrincipalCollection principals) {
         Object primary = null ;
         if (!CollectionUtils.isEmpty(principals)) {
             Collection thisPrincipals = principals.fromRealm(getName());
             if (!CollectionUtils.isEmpty(thisPrincipals)) {
                 primary = thisPrincipals.iterator().next();
             } else {
                 //no principals attributed to this particular realm.  Fall back to the 'master' primary:
                 primary = principals.getPrimaryPrincipal();
             }
         }
 
         return primary;
     }

两种情况,首先是获取当前Realm的Principals,如果有取其第一个。如果没有,则调用getPrimaryPrincipal()方法。然后看下JdbcRealm的一个简单使用: 
如果默认按照JdbcRealm的sql来作为数据库的查询来说,建表如下: 
users表:
 
?
1
2
3
4
5
6
7
8
CREATE TABLE `users` (
   `id` int (11) NOT NULL AUTO_INCREMENT,
   `username` varchar (45) NOT NULL ,
   ` password ` varchar (45) NOT NULL ,
   `password_salt` varchar (45) DEFAULT NULL ,
   PRIMARY KEY (`id`),
   UNIQUE KEY `username_UNIQUE` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

user_roles表:  
?
1
2
3
4
5
6
CREATE TABLE `user_roles` (
   `id` int (11) NOT NULL AUTO_INCREMENT,
   `username` varchar (45) DEFAULT NULL ,
   `role_name` varchar (45) DEFAULT NULL ,
   PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

roles_permissions表:  
?
1
2
3
4
5
6
CREATE TABLE `roles_permissions` (
   `id` int (11) NOT NULL AUTO_INCREMENT,
   `role_name` varchar (45) DEFAULT NULL ,
   `permission` varchar (45) DEFAULT NULL ,
   PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

文章最后会给出数据库sql文件。 
然后就是配置ini文件:
 
?
1
2
3
4
5
6
7
8
9
10
11
[main]
#realm 
dataSource=com.mchange.v2.c3p0.ComboPooledDataSource 
dataSource.driverClass=com.mysql.jdbc.Driver 
dataSource.jdbcUrl=jdbc:mysql: //localhost:3306/shiro 
dataSource.user=root 
dataSource.password=XXXXXX
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm 
jdbcRealm.dataSource=$dataSource 
jdbcRealm.permissionsLookupEnabled= true 
securityManager.realms=$jdbcRealm

使用的dataSource是c3p0的dataSource,mysql驱动也是必然不能少的,所以maven中要加入依赖:  
?
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- mysql驱动 -->
     < dependency >
         < groupId >mysql</ groupId >
         < artifactId >mysql-connector-java</ artifactId >
         < version >5.1.29</ version >
     </ dependency >   
     
     <!-- 连接池 -->
     < dependency >
         < groupId >c3p0</ groupId >
         < artifactId >c3p0</ artifactId >
         < version >0.9.1.2</ version >
     </ dependency >

为了输出方便代码更改为:  
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class ShiroTest {
 
     @Test 
     public void testHelloworld() { 
         init();
         
         Subject subject=login( "lg" , "123" );
         System.out.println(subject.hasRole( "role1" ));
         System.out.println(subject.hasRole( "role2" ));
         System.out.println(subject.hasRole( "role3" ));
     }
     
     private Subject login(String userName,String password){
          //3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证) 
         Subject subject = SecurityUtils.getSubject(); 
         UsernamePasswordToken token = new UsernamePasswordToken(userName,password); 
         subject.login(token);
         return subject;
     }
     
     private void init(){
          //1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager 
         Factory<org.apache.shiro.mgt.SecurityManager> factory = 
                 new IniSecurityManagerFactory( "classpath:shiro.ini" ); 
         //2、得到SecurityManager实例 并绑定给SecurityUtils 
         org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance(); 
         SecurityUtils.setSecurityManager(securityManager); 
     }
}

对于lg用户,在数据库中它是有两个角色的,role1和role2。所以结果为true、true、false。  
?
1
2
3
true
true
false


OK,通过。最后附上JdbcRealm的使用例子。 

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
9月前
|
安全 Java 容器
深入理解Shiro(下)
深入理解Shiro(下)
48 0
|
9月前
|
安全 Java 数据库连接
Shiro 中的 Realm
Shiro 中的 Realm
67 0
|
10月前
|
Java API 数据库
Shiro学习之Shiro基本使用(1)
Shiro学习之Shiro基本使用(1)
63 0
|
10月前
|
Java 数据库 数据安全/隐私保护
Shiro学习之Shiro基本使用(2)
Shiro学习之Shiro基本使用(2)
41 0
|
10月前
|
存储 缓存 安全
Shiro学习之Shiro简介
Shiro学习之Shiro简介
73 0
|
缓存 前端开发 程序员
Shiro实现多realm方案
前后端分离的背景下,在认证的实现中主要是两方面的内容,一个是用户登录获取到token,二是从请求头中拿到token并检验token的有效性和设置缓存。
Shiro实现多realm方案
|
安全
shiro多realm抛出异常问题
使用Shiro作为安全框架时,为了方便我们可以把异常提出为公共模块,当使用多Realm时,需要特殊处理下,不然,异常时只会抛出一种。最近写项目时,就遇到了该问题,本身项目有一个登录系统,需要多加一个OAuth2的单点登录,服务端已经OK,需要在项目中对接一下该服务端;特此记录下
309 1
|
缓存 安全 Java
Shiro(一):shiro简介
Shiro(一):shiro简介
181 0
Shiro(一):shiro简介
|
存储 安全 Java
shiro(二)shiro详解(2)
shiro(二)shiro详解
138 0
shiro(二)shiro详解(2)
|
Java 数据安全/隐私保护
shiro(二)shiro详解(1)
shiro(二)shiro详解
190 0
shiro(二)shiro详解(1)