public class UserRealm extends AuthorizingRealm {
private UserService userService = new UserServiceImpl();
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(userService.findRoles(username));
authorizationInfo.setStringPermissions(userService.findPermissions(username));
return authorizationInfo;
}
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String)token.getPrincipal();
User user = userService.findByUsername(username);
if(user == null) {
throw new UnknownAccountException();//没找到帐号
}
if(Boolean.TRUE.equals(user.getLocked())) {
throw new LockedAccountException(); //帐号锁定
}
//交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以在此判断或自定义实现
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user.getUsername(), //用户名
user.getPassword(), //密码
ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt
getName() //realm name
);
return authenticationInfo;
}
}

1、UserRealm父类AuthorizingRealm将获取Subject相关信息分成两步:获取身份验证信息(doGetAuthenticationInfo)及授权信息(doGetAuthorizationInfo);

2、doGetAuthenticationInfo获取身份验证相关信息:首先根据传入的用户名获取User信息;然后如果user为空,那么抛出没找到帐号异常UnknownAccountException;

如果user找到但锁定了抛出锁定异常LockedAccountException;最后生成AuthenticationInfo信息,交给间接父类AuthenticatingRealm使用CredentialsMatcher进行判断密码是否匹配,

如果不匹配将抛出密码错误异常IncorrectCredentialsException;另外如果密码重试此处太多将抛出超出重试次数异常ExcessiveAttemptsException;

在组装SimpleAuthenticationInfo信息时,需要传入:身份信息(用户名)、凭据(密文密码)、盐(username+salt),CredentialsMatcher使用盐加密传入的明文密码和此处的密文密码进行匹配。

3、doGetAuthorizationInfo获取授权信息:PrincipalCollection是一个身份集合,因为我们现在就一个Realm,所以直接调用getPrimaryPrincipal得到之前传入的用户名即可;然后根据用户名调用UserService接口获取角色及权限信息。

我们来看下Realm类的继承关系:

一般我们使用的是AuthorizingRealm、CasRealm、JdbcRealm,AuthorizingRealm前面已经使用过了。CasRealm、JdbcRealm的使用例子如下:  

 @Bean(name = "myCasRealm")
public CasRealm myCasRealm(EhCacheManager cacheManager) {
CasRealm casRealm = new CasRealm();
casRealm.setCacheManager(cacheManager);
casRealm.setCasServerUrlPrefix(ShiroCasConfig.casServerUrlPrefix);
// 客户端回调地址 https://localhost:8081/cas 用来接收cas服务端票据,并且必须要和下面过滤器拦截的地址一致,拦截之后交由casFilter处理验证票据
casRealm.setCasService(ShiroCasConfig.shiroServerUrlPrefix + ShiroCasConfig.casFilterUrlPattern);
return casRealm;
} @Bean(name="myJdbcRealm")
public JdbcRealm myJdbcRealm(@Qualifier("shirocasDataSource") DataSource shirocasDataSource){
JdbcRealm jdbcRealm = new JdbcRealm();
jdbcRealm.setDataSource(shirocasDataSource);
String authenticationQuery = "select password from account where name=?";
jdbcRealm.setAuthenticationQuery(authenticationQuery);
String userRolesQuery="SELECT NAME FROM role WHERE id =(SELECT roleId FROM account_role WHERE userId = (SELECT id FROM account WHERE NAME = ?))";
jdbcRealm.setUserRolesQuery(userRolesQuery);
String permissionsQuery = "SELECT NAME FROM permission WHERE id in (SELECT permissionId FROM permission_role WHERE (SELECT id FROM role WHERE NAME = ?))";
jdbcRealm.setPermissionsQuery(permissionsQuery);
jdbcRealm.setPermissionsLookupEnabled(true);
return jdbcRealm;
}

casRealm的部分源码:

/**
* This realm implementation acts as a CAS client to a CAS server for authentication and basic authorization.
* <p/>
* This realm functions by inspecting a submitted {@link org.apache.shiro.cas.CasToken CasToken} (which essentially
* wraps a CAS service ticket) and validates it against the CAS server using a configured CAS
* {@link org.jasig.cas.client.validation.TicketValidator TicketValidator}.
* <p/>
/**
* Authenticates a user and retrieves its information.
*
* @param token the authentication token
* @throws AuthenticationException if there is an error during authentication.
*/
@Override
@SuppressWarnings("unchecked")
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
CasToken casToken = (CasToken) token;
if (token == null) {
return null;
} String ticket = (String)casToken.getCredentials();
if (!StringUtils.hasText(ticket)) {
return null;
} TicketValidator ticketValidator = ensureTicketValidator(); try {
// contact CAS server to validate service ticket
Assertion casAssertion = ticketValidator.validate(ticket, getCasService());
// get principal, user id and attributes
AttributePrincipal casPrincipal = casAssertion.getPrincipal();
String userId = casPrincipal.getName();
log.debug("Validate ticket : {} in CAS server : {} to retrieve user : {}", new Object[]{
ticket, getCasServerUrlPrefix(), userId
}); Map<String, Object> attributes = casPrincipal.getAttributes();
// refresh authentication token (user id + remember me)
casToken.setUserId(userId);
String rememberMeAttributeName = getRememberMeAttributeName();
String rememberMeStringValue = (String)attributes.get(rememberMeAttributeName);
boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);
if (isRemembered) {
casToken.setRememberMe(true);
}
// create simple authentication info
List<Object> principals = CollectionUtils.asList(userId, attributes);
PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());
return new SimpleAuthenticationInfo(principalCollection, ticket);
} catch (TicketValidationException e) {
throw new CasAuthenticationException("Unable to validate ticket [" + ticket + "]", e);
}
} /**
* Retrieves the AuthorizationInfo for the given principals (the CAS previously authenticated user : id + attributes).
*
* @param principals the primary identifying principals of the AuthorizationInfo that should be retrieved.
* @return the AuthorizationInfo associated with this principals.
*/
@Override
@SuppressWarnings("unchecked")
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// retrieve user information
SimplePrincipalCollection principalCollection = (SimplePrincipalCollection) principals;
List<Object> listPrincipals = principalCollection.asList();
Map<String, String> attributes = (Map<String, String>) listPrincipals.get();
// create simple authorization info
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// add default roles
addRoles(simpleAuthorizationInfo, split(defaultRoles));
// add default permissions
addPermissions(simpleAuthorizationInfo, split(defaultPermissions));
// get roles from attributes
List<String> attributeNames = split(roleAttributeNames);
for (String attributeName : attributeNames) {
String value = attributes.get(attributeName);
addRoles(simpleAuthorizationInfo, split(value));
}
// get permissions from attributes
attributeNames = split(permissionAttributeNames);
for (String attributeName : attributeNames) {
String value = attributes.get(attributeName);
addPermissions(simpleAuthorizationInfo, split(value));
}
return simpleAuthorizationInfo;
}

JdbcRealm的部分源码:

  /**
* The default query used to retrieve account data for the user.
*/
protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password from users where username = ?"; /**
* The default query used to retrieve account data for the user when {@link #saltStyle} is COLUMN.
*/
protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = "select password, password_salt from users where username = ?"; /**
* The default query used to retrieve the roles that apply to a user.
*/
protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?"; /**
* The default query used to retrieve permissions that apply to a particular role.
*/
protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?"; 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)[];
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[];
salt = queryResults[];
break;
case EXTERNAL:
password = getPasswordForUser(conn, username)[];
salt = getSaltForUser(username);
} if (password == null) {
throw new UnknownAccountException("No account found for user [" + username + "]");
} 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;
} /**
* This implementation of the interface expects the principals collection to return a String username keyed off of
* this realm's {@link #getName() name}
*
* @see #getAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)
*/
@Override
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; } protected Set<String> getRoleNamesForUser(Connection conn, String username) throws SQLException {
PreparedStatement ps = null;
ResultSet rs = null;
Set<String> roleNames = new LinkedHashSet<String>();
try {
ps = conn.prepareStatement(userRolesQuery);
ps.setString(, username); // Execute query
rs = ps.executeQuery(); // Loop over results and add each returned role to a set
while (rs.next()) { String roleName = rs.getString(); // Add the role to the list of names if it isn't null
if (roleName != null) {
roleNames.add(roleName);
} else {
if (log.isWarnEnabled()) {
log.warn("Null role name found while retrieving role names for user [" + username + "]");
}
}
}
} finally {
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeStatement(ps);
}
return roleNames;
} protected Set<String> getPermissions(Connection conn, String username, Collection<String> roleNames) throws SQLException {
PreparedStatement ps = null;
Set<String> permissions = new LinkedHashSet<String>();
try {
ps = conn.prepareStatement(permissionsQuery);
for (String roleName : roleNames) { ps.setString(, roleName); ResultSet rs = null; try {
// Execute query
rs = ps.executeQuery(); // Loop over results and add each returned role to a set
while (rs.next()) { String permissionString = rs.getString(); // Add the permission to the set of permissions
permissions.add(permissionString);
}
} finally {
JdbcUtils.closeResultSet(rs);
} }
} finally {
JdbcUtils.closeStatement(ps);
} return permissions;
}

AuthenticationToken

AuthenticationToken用于收集用户提交的身份(如用户名)及凭据(如密码):

public interface AuthenticationToken extends Serializable {
Object getPrincipal(); //身份
Object getCredentials(); //凭据
}

扩展接口RememberMeAuthenticationToken:提供了“boolean isRememberMe()”现“记住我”的功能;

扩展接口是HostAuthenticationToken:提供了“String getHost()”方法用于获取用户“主机”的功能。

Shiro提供了一个直接拿来用的UsernamePasswordToken,用于实现用户名/密码Token组,另外其实现了RememberMeAuthenticationToken和HostAuthenticationToken,可以实现记住我及主机验证的支持。

AuthenticationInfo

public interface AuthenticationInfo extends Serializable {
PrincipalCollection getPrincipals();
Object getCredentials();
}

AuthenticationInfo有两个作用:

1、如果Realm是AuthenticatingRealm子类,则提供给AuthenticatingRealm内部使用的CredentialsMatcher进行凭据验证;(如果没有继承它需要在自己的Realm中自己实现验证);

2、提供给SecurityManager来创建Subject(提供身份信息);

CredentialsMatcher:

public interface CredentialsMatcher {
boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info);
}

它的实现:

 public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenCredentials = getCredentials(token);
Object accountCredentials = getCredentials(info);
return equals(tokenCredentials, accountCredentials);
}

PrincipalCollection

因为我们可以在Shiro中同时配置多个Realm,所以呢身份信息可能就有多个;因此其提供了PrincipalCollection用于聚合这些身份信息:

public interface PrincipalCollection extends Iterable, Serializable {
Object getPrimaryPrincipal(); //得到主要的身份
<T> T oneByType(Class<T> type); //根据身份类型获取第一个
<T> Collection<T> byType(Class<T> type); //根据身份类型获取一组
List asList(); //转换为List
Set asSet(); //转换为Set
Collection fromRealm(String realmName); //根据Realm名字获取
Set<String> getRealmNames(); //获取所有身份验证通过的Realm名字
boolean isEmpty(); //判断是否为空
}

因为PrincipalCollection聚合了多个,此处最需要注意的是getPrimaryPrincipal,如果只有一个Principal那么直接返回即可,如果有多个Principal,则返回第一个(因为内部使用Map存储,所以可以认为是返回任意一个);

AuthorizationInfo

AuthorizationInfo用于聚合授权信息的:

public interface AuthorizationInfo extends Serializable {
Collection<String> getRoles(); //获取角色字符串信息
Collection<String> getStringPermissions(); //获取权限字符串信息
Collection<Permission> getObjectPermissions(); //获取Permission对象信息
}

当我们使用AuthorizingRealm时,如果身份验证成功,在进行授权时就通过doGetAuthorizationInfo方法获取角色/权限信息用于授权验证。

Shiro提供了一个实现SimpleAuthorizationInfo,大多数时候使用这个即可。

摘自:第六章 Realm及相关对象——《跟我学Shiro》

shiro的Realm的更多相关文章

  1. shiro自定义Realm

    1.1 自定义Realm 上边的程序使用的是shiro自带的IniRealm,IniRealm从ini配置文件中读取用户的信息,大部分情况下需要从系统的数据库中读取用户信息,所以需要自定义realm. ...

  2. Shiro笔记(四)Shiro的realm认证

    认证流程: 1.获取当前Subject.调用SecurityUtils.getSubject(); 2.测试当前用户是否已经被认证,即是否已经登录,调用Subject的isAurhenticated( ...

  3. Shiro中Realm

    6.1 Realm [2.5 Realm]及[3.5 Authorizer]部分都已经详细介绍过Realm了,接下来再来看一下一般真实环境下的Realm如何实现. 1.定义实体及关系   即用户-角色 ...

  4. 自定义shiro的Realm实现和CredentialsMatcher实现以及Token实现

    Realm是shiro比较核心的接口,简单说它的实现类就是校验用户输入的账号信息的地方.如果想自定义实现一般的配置文件如下: <!--自定义Realm 继承自AuthorizingRealm - ...

  5. 权限框架 - shiro 自定义realm

    上篇文章中是使用的默认realm来实现的简单登录,这仅仅只是个demo,真正项目中使用肯定是需要连接数据库的 首先创建自定义realm文件,如下: 在shiro中注入自定义realm的完全限定类名: ...

  6. shiro多Realm第一次调用不生效问题

    1. 由于最近自己写的一个项目上用到了多realm的使用,遇到了一个这样的问题: 1. 自己继承了BasicHttpAuthenticationFilter,实现了获取token,然后直接请求api的 ...

  7. shiro自定义realm支持MD5算法认证(六)

    1.1     散列算法 通常需要对密码 进行散列,常用的有md5.sha, 对md5密码,如果知道散列后的值可以通过穷举算法,得到md5密码对应的明文. 建议对md5进行散列时加salt(盐),进行 ...

  8. shiro自定义realm认证(五)

    上一节介绍了realm的作用: realm:需要根据token中的身份信息去查询数据库(入门程序使用ini配置文件),如果查到用户返回认证信息,如果查询不到返回null.token就相当于是对用户输入 ...

  9. Shiro自定义Realm时用注解的方式注入父类的credentialsMatcher

    用Shiro做登录权限控制时,密码加密是自定义的. 数据库的密码通过散列获取,如下,算法为:md5,盐为一个随机数字,散列迭代次数为3次,最终将salt与散列后的密码保存到数据库内,第二次登录时将登录 ...

  10. shiro双realm验证

    假设现在有这样一种需求:存在两张表user和admin,分别记录普通用户和管理员的信息.并且现在要实现普通用户和管理员的分开登录,即需要两个Realm——UserRealm和AdminRealm,分别 ...

随机推荐

  1. The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Cha

    The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Cha ...

  2. js Date操作

    new Date(new Date().getTime() - 24 * 24 * 60 * 60 * 1000)类似C#中的AddDays,返回一个月前的时间  //时间格式化方法        v ...

  3. js节流防抖应用场景,以及在vue中节流防抖的具体实现

    故事背景: 项目有个需求是输入框在输入的时候进行搜索,展示下拉数据,但是没必要输入一个字都进行搜索,所以想到了在输入结束200毫秒后再进行搜索,从而引出来了 js的节流(throttle),防抖(de ...

  4. js转译html标签

    api返回的接口数据是这样的"<div>测试数据</div>" js拼接html时会过滤掉<div></div> 所以需要对< ...

  5. iOS -转载-字符串是否为空判断方法

    - (BOOL)blankString{ if (![self isKindOfClass:[NSString class]] ){ return YES; } if ([self isEqual:[ ...

  6. IntelliJ IDEA2017 java连接mysql数据库并查询数据

    最近自己开始重新学习java基础了,做java开发不可避免要处理数据库,由于好久不写java了,对idea也有点陌生了.所以这里写篇用jdbc来连接mysql的文章 至于mysql怎么装,请自行百度  ...

  7. JDBC批量操作性能提升

    JDBC 当使用INSERT INTO....VALUES()语句批量插入的时候,应该使用JDBC的PreparedStatement的批量操作方法,而不是採用一条一条运行的方法. 比如(来源:htt ...

  8. OpenSSL Heart Bleed 如何修复

     一 . 前言  这两天这个事件沸沸扬扬啊,有了这个bug黑客在电脑前动动手指就能获取各大电商网站.各大银行用户的用户名和密码了,屌爆了 BUG具体内容 : http://heartbleed.com ...

  9. docker 中安装 FastDFS 总结

    如题,参考各资料后,安装FastDFS总结.基于已有docker镜像 https://hub.docker.com/r/luhuiguo/fastdfs/ docker pull luhuiguo/f ...

  10. Python IDLE或shell中切换路径

    在Python自带的编辑器IDLE中或者python shell中不能使用cd命令,那么跳到目标路径呢.方法是使用os包下的相关函数实现路径切换功能. import os  os.getcwd() # ...