Shiro多realm实践
开发中突然接到一个新的需求,需要在原始系统中加入一个单点登录的功能,这不是很简单的一个需求吗,几行代码就可以搞定的事。拿到文档后直接开搞,发现用户方提供了一个token查询用户信息的接口,那么需求变得更清晰了,写一个链接,参数就是用户方的token,完事拿着token去请求平台接口,将获取到的用户信息直接注册到平台。下面写一点伪代码意思一下:
public void sso(String token){
Result result = HttpUtil.post(url+token);
if(result.isOk()){
User user = result.data();
if(!check(user)){
// 如果用户在系统中不存在
register(user);
}
//将单点登录的用户登录系统中
login(user.getLoginName(),user.getPassword());
}
}
以上代码基本解决了登录问题,本来这样就可以交差了,但是突然想到,系统还有锁屏以及定时修改密码等问题,因此单点登录需要绕过密码登录,但是系统使用的是shiro,realm是用的密码和账号授权很显然无法满足了。因此笔者想到了以下两种方案:
- 单点登录完成,自动初始化密码,保证库内密码一致
- 改造系统登录模块,增加单独针对sso的realm
很显然,这里第二种方案更靠谱,下面开始登录改造。
一、创建单点登录需要的Token类
使用shiro登录时默认会使用UsernamePasswordToken,我们这边为了实现忽略密码的方式,这里直接选择继承BearerToken,进行自定义token,这里贴出部分代码,可以根据自己需求就行更改
public class SSOToken extends BearerToken {
private SysUser user;
public SSOToken(SysUser user,String token) {
super(token);
this.user = user;
}
public SysUser getUser() {
return user;
}
public void setUser(SysUser user) {
this.user = user;
}
}
二、创建单点登录使用的realm
众所周知,shiro登录及权限获取的逻辑主要是在realm中实现,默认情况下,我们创建一个realm用于密码登录即可,这里为实现用户名密码和token两种登录方式,需要创建两个realm,此处自己贴出自定义代码,读者可按需修改
public class SSORealm extends AuthorizingRealm implements Serializable {
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Set<String> roles = new HashSet<>();
roles.add("admin");
info.setRoles(roles);
return info;
}
/**
* 登录认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
SSOToken upToken = (SSOToken) token;
// 获取sso登录传入的user实体
User user = upToken.getUser();
//不传入拒绝登录
if(user==null){
throw new UnknownAccountException();
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, upToken.getToken(), getName());
return info;
}
}
三、自定义身份验证器
编写一个自定义的身份验证器,根据条件选择指定的realm进行登录验证。
public enum LoginType {
SSO("SSORealm"),USERNAME("UserRealm");
private final String type;
LoginType(String type) {
this.type = type;
}
@Override
public String toString() {
return type;
}
}
@Component
public class CustomModularRealmAuthenticator extends ModularRealmAuthenticator {
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
Collection<Realm> typeRealms = new ArrayList<>();
if (authenticationToken instanceof SSOToken) {
// 单点登录方式选择SSORealm
for (Realm realm : realms) {
if (realm.getName().contains(LoginType.SSO.toString())) {
typeRealms.add(realm);
}
}
} else if (authenticationToken instanceof UsernamePasswordToken) {
//用户名密码方式登录,选择UserRealm
for (Realm realm : realms) {
if (realm.getName().contains(LoginType.USERNAME.toString())) {
typeRealms.add(realm);
}
}
}
if (typeRealms.size() == 1) {
return doSingleRealmAuthentication(typeRealms.iterator().next(), authenticationToken);
}
return doMultiRealmAuthentication(typeRealms, authenticationToken);
}
}
四、编写配置文件
完成上述工作后,多realm配置基本完成了,不过由于用到了BearToken,还需要对shiro的配置类进行一些改造
@Configuration
public class ShiroConfig {
@Autowired
private ModularRealmAuthenticator customModularRealmAuthenticator;
/**
* 自定义Realm
*/
@Bean
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
return userRealm;
}
@Bean
public SSORealm ssoRealm() {
SSORealm userRealm = new SSORealm();
userRealm.setAuthenticationTokenClass(BearerToken.class);
return userRealm;
}
/**
* 安全管理器
*/
@Bean
public DefaultWebSecurityManager securityManager(Collection<Realm> realms) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置多realm识别
securityManager.setAuthenticator(customModularRealmAuthenticator);
// 设置realm.
securityManager.setRealms(realms);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// Shiro的核心安全接口,这个属性是必须的
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 身份认证失败,则跳转到登录页面的配置
shiroFilterFactoryBean.setLoginUrl("/login");
// 权限认证失败,则跳转到指定页面
shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");
// Shiro连接约束配置,即过滤链的定义
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/normal", "anon");
filterChainDefinitionMap.put("/sso", "anon");
filterChainDefinitionMap.put("/**", "authc");
Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
shiroFilterFactoryBean.setFilters(filters);
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
}
五、总结
至此,单点登录的多realm实现基本完成了,由于项目原因,贴出的代码基本都是示例,读者可以按需进行修改,需要新增的就是realm、自定义身份验证器,还有就是需要对shiro config进行一些调整,全部的代码示例已经上传到github,如有需要可以自行下载。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。