光是下面的代码只能实现相同ip不同端口的跨域iframe自动登录,如果是不同域名的跨域除了下面的代码之外还需要借助nginx的代理,将不同域名的跨域转换为相同ip不同端口的跨域,nginx代理在附一中
下面的为A系统登录后跳转的首个页面,在A系统的页面中内嵌iframe,对B系统进行免密自动登录
!<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<iframe src="http://localhost:9090/jeeplus_war/a/unifiedAuthentication/zzz?cid=admin"></iframe>
</body>
</html>
B系统进行自动登录的接口
@Controller
@RequestMapping(value = "unifiedAuthentication")
public class UnifiedAuthenticationController extends BaseController {
@RequestMapping(value = "zzz")
public String singleLoginzz(HttpServletRequest request,HttpServletResponse response,String cid) {
//TODO 在实际项目中需要在这个接口中添加数字签名认证,保证系统的安全性
Subject currentUser = SecurityUtils.getSubject();
//在这里进行shiro框架的免密自动登录
currentUser.login(new UsernamePasswordToken(cid,"这个密码是随便填的,后续登录验证中并没有使用到".toCharArray(),true));
//进行在第三方进行自动登录时,由于是在iframe中进行的跨域名访问,没有设置cookie的权限,因此需要添加下面两行代码
response.setHeader("Set-Cookie", "Key=Value;SameSite=None;Secure");
response.setHeader("Access-Control-Allow-Origin","*");
return "redirect:"+"/";
}
}
B系统的UsernamePasswordToken需要设置可以免除密码的登录
public class UsernamePasswordToken extends org.apache.shiro.authc.UsernamePasswordToken {
private static final long serialVersionUID = 1L;
private String captcha;
private boolean mobileLogin;
private boolean noPassword;
public boolean isNoPassword() {
return noPassword;
}
public void setNoPassword(boolean noPassword) {
this.noPassword = noPassword;
}
public UsernamePasswordToken() {
super();
}
public UsernamePasswordToken(String username, char[] password,boolean noPassword) {
super(username,password);
this.noPassword=noPassword;
}
public UsernamePasswordToken(String username, char[] password,
boolean rememberMe, String host, String captcha, boolean mobileLogin) {
super(username, password, rememberMe, host);
this.captcha = captcha;
this.mobileLogin = mobileLogin;
}
public String getCaptcha() {
return captcha;
}
public void setCaptcha(String captcha) {
this.captcha = captcha;
}
public boolean isMobileLogin() {
return mobileLogin;
}
}
下面为B系统免密登录相关代码
B系统的自定义实现的验证比较器HashedCredentialsMatcherAndNoPassword
package com.jeeplus.modules.sys.security;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SaltedAuthenticationInfo;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.codec.Hex;
import org.apache.shiro.crypto.hash.AbstractHash;
import org.apache.shiro.crypto.hash.Hash;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.StringUtils;
/**
* @author: wyj
* @date: 2021/06/25
*/
public class HashedCredentialsMatcherAndNoPassword extends SimpleCredentialsMatcher {
private String hashAlgorithm;
private int hashIterations;
private boolean hashSalted;
private boolean storedCredentialsHexEncoded;
public HashedCredentialsMatcherAndNoPassword() {
this.hashAlgorithm = null;
this.hashSalted = false;
this.hashIterations = 1;
this.storedCredentialsHexEncoded = true;
}
public HashedCredentialsMatcherAndNoPassword(String hashAlgorithmName) {
this();
if (!StringUtils.hasText(hashAlgorithmName)) {
throw new IllegalArgumentException("hashAlgorithmName cannot be null or empty.");
} else {
this.hashAlgorithm = hashAlgorithmName;
}
}
public String getHashAlgorithmName() {
return this.hashAlgorithm;
}
public void setHashAlgorithmName(String hashAlgorithmName) {
this.hashAlgorithm = hashAlgorithmName;
}
public boolean isStoredCredentialsHexEncoded() {
return this.storedCredentialsHexEncoded;
}
public void setStoredCredentialsHexEncoded(boolean storedCredentialsHexEncoded) {
this.storedCredentialsHexEncoded = storedCredentialsHexEncoded;
}
/** @deprecated */
@Deprecated
public boolean isHashSalted() {
return this.hashSalted;
}
/** @deprecated */
@Deprecated
public void setHashSalted(boolean hashSalted) {
this.hashSalted = hashSalted;
}
public int getHashIterations() {
return this.hashIterations;
}
public void setHashIterations(int hashIterations) {
if (hashIterations < 1) {
this.hashIterations = 1;
} else {
this.hashIterations = hashIterations;
}
}
/** @deprecated */
@Deprecated
protected Object getSalt(AuthenticationToken token) {
return token.getPrincipal();
}
protected Object getCredentials(AuthenticationInfo info) {
Object credentials = info.getCredentials();
byte[] storedBytes = this.toBytes(credentials);
if (credentials instanceof String || credentials instanceof char[]) {
if (this.isStoredCredentialsHexEncoded()) {
storedBytes = Hex.decode(storedBytes);
} else {
storedBytes = Base64.decode(storedBytes);
}
}
AbstractHash hash = this.newHashInstance();
hash.setBytes(storedBytes);
return hash;
}
//免密登录的修改
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenHashedCredentials = this.hashProvidedCredentials(token, info);
Object accountCredentials = this.getCredentials(info);
UsernamePasswordToken token1 = (UsernamePasswordToken) token;
if(token1.isNoPassword()){
return true;
}
return this.equals(tokenHashedCredentials, accountCredentials);
}
protected Object hashProvidedCredentials(AuthenticationToken token, AuthenticationInfo info) {
Object salt = null;
if (info instanceof SaltedAuthenticationInfo) {
salt = ((SaltedAuthenticationInfo)info).getCredentialsSalt();
} else if (this.isHashSalted()) {
salt = this.getSalt(token);
}
return this.hashProvidedCredentials(token.getCredentials(), salt, this.getHashIterations());
}
private String assertHashAlgorithmName() throws IllegalStateException {
String hashAlgorithmName = this.getHashAlgorithmName();
if (hashAlgorithmName == null) {
String msg = "Required 'hashAlgorithmName' property has not been set. This is required to execute the hashing algorithm.";
throw new IllegalStateException(msg);
} else {
return hashAlgorithmName;
}
}
protected Hash hashProvidedCredentials(Object credentials, Object salt, int hashIterations) {
String hashAlgorithmName = this.assertHashAlgorithmName();
return new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
}
protected AbstractHash newHashInstance() {
String hashAlgorithmName = this.assertHashAlgorithmName();
return new SimpleHash(hashAlgorithmName);
}
}
B系统的realm中指定使用上面的比较器
SystemAuthorizingRealm
@Service
//@DependsOn({"userMapper","roleMapper","menuMapper"})
public class SystemAuthorizingRealm extends AuthorizingRealm {
@PostConstruct
public void initCredentialsMatcher() {
// HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(SystemService.HASH_ALGORITHM);
HashedCredentialsMatcherAndNoPassword matcher = new HashedCredentialsMatcherAndNoPassword(SystemService.HASH_ALGORITHM);
matcher.setHashIterations(SystemService.HASH_INTERATIONS);
setCredentialsMatcher(matcher);
}
}
附一
通过nginx将不同域名的跨域转换为相同ip不同端口的跨域
设
A系统地址:http://888.888.888:7070
B系统地址:http://999.999:999:8080
在nginx.conf中配置代理
upstream bserver{
#B系统的ip地址:B系统的端口号
server 999.999:999:8080;
keepalive 2000;
}
server {
#与A系统不同的端口号
listen 9000;
server_name localhost;
client_max_body_size 1024M;
location / {
proxy_pass http://bserver/;
proxy_set_header Host $host:$server_port;
}
}
在A系统中嵌入iframe的页面
!<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<iframe src="http://888.888.888:9000/jeeplus_war/a/unifiedAuthentication/zzz?cid=admin"></iframe>
</body>
</html>
整个流程为访问A系统:http://888.888.888:7070,A系统的iframe页面去请求http://888.888.888:9000,通过nginx请求转发到B系统http://999.999:999:8080