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,如有须要能够自行下载。