1.前言:
最近在做一个基于SpringBoot 2 + shiro +vue2 项目集成山东通采用Auth2 的方式进行单点登陆。里面很多坑,在此总结记录一下,方便以后查阅。
应用服务器互联网区、山东通接口在政务外网区、前置机在政务外网区,可以访问应用服务器。需要在前置机上配置端口转发。
单点登陆流程:
用户通过山东通客户端访问系统。
1、应用系统调用6.3获取授权码(code)接口获取用户访问临时授权码;
2、应用系统调用6.2获取令牌(token)接口获取应用访问令牌;
3、应用系统使用6.2获取的访问令牌和6.3获取的授权码获取统一用户编码,接口对应的是6.4获取登录用户统一用户编码接口;
4、应用系统使用6.4获取的统一用户编码调用6.5获取用户信息接口获取用户的详细信息(包括手机号码)
6、应用系统使用6.5获取的用户手机号码调用手机号码登录接口实现用户登录;
2.配置步骤
2.1 SpringBoot2 后台配置
1. 新增SdtSsoToken 实现AuthenticationToken
package com.dechnic.omsdc.server.admin.shiro.sdtSso;/**
* @className: SdtSsoToken
* @author: houqd
* @date: 2022/11/8
**/
import lombok.*;
import org.apache.shiro.authc.AuthenticationToken;
/**
*@description:
*@author:houqd
*@time: 2022/11/8 13:40
*
*/
//@Data
//@AllArgsConstructor
//@RequiredArgsConstructor
//@NoArgsConstructor
public class SdtSsoToken implements AuthenticationToken {
// @NonNull
private String code;
// @NonNull
private String accessToken;
private String principal;
public SdtSsoToken( String code, String accessToken) {
this.code = code;
this.accessToken = accessToken;
}
public String getCode() {
return code;
}
public String getAccessToken() {
return accessToken;
}
@Override
public Object getPrincipal() {
return principal;
}
public void setPrincipal(String principal) {
this.principal = principal;
}
@Override
public Object getCredentials() {
return code;
}
}
必坑1: 此处不要使用lombok 注解,使用后登陆验证的时候principle 为null ,验证失败。
2. 新增 SdtSsoRealm 继承 AuthorizingRealm
package com.dechnic.omsdc.server.admin.shiro.sdtSso;/**
* @className: SdtSsoRealm
* @author: houqd
* @date: 2022/11/8
**/
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.dechnic.omsdc.server.admin.entity.TSysPerm;
import com.dechnic.omsdc.server.admin.entity.TSysRole;
import com.dechnic.omsdc.server.admin.entity.TSysUser;
import com.dechnic.omsdc.server.admin.service.TSysRoleService;
import com.dechnic.omsdc.server.admin.service.TSysUserService;
import com.dechnic.omsdc.server.common.entity.SsoUserBind;
import com.dechnic.omsdc.server.common.mapper.SsoUserBindMapper;
import com.dechnic.omsdc.server.common.service.ISdtSsoApiService;
import com.dechnic.omsdc.server.common.utils.LightWeightEncoder;
import com.dechnic.omsdc.server.common.utils.SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Resource;
import java.util.List;
/**
*@description:
*@author:houqd
*@time: 2022/11/8 13:52
*
*/
@Slf4j
public class SdtSsoRealm extends AuthorizingRealm {
private static final String DEFAULT_CHARSET = "UTF-8";
@Autowired
private SdtSsoConfigProps sdtSsoConfigProps;
@Autowired
TSysUserService tSysUserService;
@Resource
SsoUserBindMapper ssoUserBindMapper;
@Autowired
private TSysRoleService roleService;
@Autowired
private ISdtSsoApiService sdtSsoApiService;
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof SdtSsoToken;
}
// 获取授权信息
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
TSysUser user = (TSysUser) principals.getPrimaryPrincipal();
List<TSysRole> roleList = user.getRoleList();
if (roleList == null || roleList.size() == 0) {
roleList = roleService.getUserRoleInfo(user.getId(), TSysPerm.PERM_TYPE.MENU);
if(roleList == null || roleList.size()==0){
throw new AuthenticationException(user.getUserName()+"尚未分配角色,请联系管理员!");
}
user.setRoleList(roleList);
}
for (TSysRole role : roleList) {
authorizationInfo.addRole(role.getRoleName());
for (TSysPerm perm : role.getPermList()) {
if (StringUtils.isNotEmpty(perm.getPermName())) {
authorizationInfo.addStringPermission(perm.getPermName());
}
}
}
return authorizationInfo;
}
// 获取身份验证相关信息
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
SdtSsoToken sdtSsoToken = (SdtSsoToken) token;
String accessToken = sdtSsoToken.getAccessToken();
String code = sdtSsoToken.getCode();
String ssoUserId = this.sdtSsoApiService.getSsoUserId(accessToken, code);
String ssoUserPhoneNo = this.sdtSsoApiService.getSsoUserPhoneNo(accessToken, ssoUserId);
TSysUser user = getTsysUserBySsoUserId(ssoUserPhoneNo);
List<TSysRole> roleList = user.getRoleList();
if (roleList == null || roleList.size() == 0) {
roleList = roleService.getUserRoleInfo(user.getId(), TSysPerm.PERM_TYPE.MENU);
if(roleList == null || roleList.size()==0){
throw new AuthenticationException(user.getUserName()+"尚未分配角色,请联系管理员!");
}
user.setRoleList(roleList);
}
if (user == null) {
throw new UnknownAccountException("用户名或密码错误!");
}
if (user.getState() == TSysUser.STATE.INVALID) {
// 账户冻结
throw new LockedAccountException("账号已被锁定,请联系管理员");
}
SimpleAuthenticationInfo authenticationInfo =
new SimpleAuthenticationInfo(user, code, getName());
return authenticationInfo;
}
private TSysUser getTsysUserBySsoUserId(String ssoUserID) {
TSysUser user = null;
SsoUserBind ssoUserBind = ssoUserBindMapper.selectBySSoUserId(ssoUserID);
if (ssoUserBind != null){
user = this.tSysUserService.selectById(ssoUserBind.getOmdsUserId());
List<TSysRole> roleList = user.getRoleList();
if (roleList == null || roleList.size() == 0) {
roleList = roleService.getUserRoleInfo(user.getId(), TSysPerm.PERM_TYPE.MENU);
user.setRoleList(roleList);
}
}
return user;
}
}
3. ShiroConfig 配置
package com.dechnic.omsdc.server.admin.shiro;
import com.dechnic.omsdc.server.admin.shiro.oauth2.client.OAuth2Realm;
import com.dechnic.omsdc.server.admin.shiro.sdtSso.SdtSsoRealm;
import com.dechnic.omsdc.server.admin.shiro.sdtSso.SdtSsoToken;
import com.dechnic.omsdc.server.admin.shiro.zyoaSso.ZyoaSsoRealm;
import com.dechnic.omsdc.server.common.utils.SpringContextUtil;
import lombok.extern.slf4j.Slf4j;
import net.sf.ehcache.CacheManager;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.io.ResourceUtils;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.mgt.SessionsSecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.HandlerExceptionResolver;
import javax.servlet.Filter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Configuration
@Slf4j
public class ShiroConfig {
private static long globalSessionTimeOut = -1000L;
@Value("${prop.clientAddress}")
private String clientAddress;
/**
* @description:@RequiresPermissions这类shiro权限的注解, 是DefaultAdvisorAutoProxyCreator这个bean设置之后才会生效的
* @params:
* @return:
* @author: houqd
* @time: 2020/1/30 17:20
*/
@Bean
public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator =
new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
defaultAdvisorAutoProxyCreator.setUsePrefix(true);
return defaultAdvisorAutoProxyCreator;
}
/**
* 开启shiro aop注解支持. 使用代理方式;所以需要开启代码支持;
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 自定义安全域,用户验证、权限等数据在此提供
*
* @return
*/
@Bean
public RealmUserName myShiroRealm() {
RealmUserName realmUserName = new RealmUserName();
realmUserName.setCredentialsMatcher(hashedCredentialsMatcher());
return realmUserName;
}
/**
* 单点登陆realm
*
* @return
*/
@Bean
public OAuth2Realm oAuth2Realm() {
OAuth2Realm oA