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/>