spring-boot客户端集成shiro-cas实现单点登录解
2021-10-07 本文已影响0人
清远_03d9
1. spring-boot客户端集成shiro-cas实现单点登录解决方案
1. maven依赖引入
<!--shiro start-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-cas</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
<!--shiro end-->
2. application.properties配置
# http://localhost:9000/sso/auth:sa-token服务的统一认证地址,http://127.0.0.1:8082/jdtbsh/sso/cas为客户端的重定向地址
shiro.loginUrl=http://localhost:9000/sso/auth?redirect=http://127.0.0.1:8082/jdtbsh/sso/cas
# sa-token服务的登出接口
shiro.logoutUrl=http://localhost:9000/sso/logout
# 登陆成功后客户端跳转页面
shiro.successUrl=/main/index
shiro.failureUrl
# sa-token服务地址前缀
shiro.cas.serverUrlPrefix=http://localhost:9000
# 客户端应用的CAS服务URL,用于接收和处理CAS服务端的Ticket
shiro.cas.service=http://127.0.0.1:8082/jdtbsh/main/index
# sa-token秘钥
sa-token.secretkey=DIITabc123
3. ShiroConfiguration自定义bean
@Configuration
public class ShiroConfiguration {
/**
*成功跳转地址
*/
@Value("${shiro.successUrl}")
private String successUrl;
/**
* 失败跳转地址
*/
@Value("${shiro.failureUrl}")
private String failureUrl;
/**
* 登录地址
*/
@Value("${shiro.loginUrl}")
private String loginUrl;
/**
* 登出地址
*/
@Value("${shiro.logoutUrl}")
private String logoutUrl;
/**
* 回调地址
*/
@Value("${shiro.cas.service}")
private String casService;
/**
* CAS 地址
*/
@Value("${shiro.cas.serverUrlPrefix}")
private String casServerUrlPrefix;
@Value("${lxgz.dlfs}")
private String dlfs;
@Bean
public CasFilter casFilter(){
return new CasFilter();
}
/**
* ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意:单独一个ShiroFilterFactoryBean配置是或报错的
* 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
//配置自定义casFilter
Map<String, Filter> filters = new LinkedHashMap<>();
CasFilter casFilter = new CasFilter();
casFilter.setFailureUrl(failureUrl);
casFilter.setSuccessUrl(successUrl);
filters.put("cas", casFilter);
LogoutFilter logoutFilter = new LogoutFilter();
logoutFilter.setRedirectUrl(logoutUrl);
filters.put("logout", logoutFilter);
shiroFilterFactoryBean.setFilters(filters);
//未授权跳转页面
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
//service指定了登录成功后的回调地址,回调/cas将被CasFilter拦截,获取服务端返回的Service Ticket进行登录
shiroFilterFactoryBean.setLoginUrl(loginUrl);
//登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl(successUrl);
// 拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
//拦截CAS Server返回的ticket
filterChainDefinitionMap.put("/sso/cas", "cas");
//退出登录
//filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/login_sso", "anon");
filterChainDefinitionMap.put("/**", "authc");
filterChainDefinitionMap.put("/", "authc");
// 未授权界面;
//shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 自定义Realm
* @return
*/
@Bean
public ShiroRealm casRealm(){
ShiroRealm casRealm = new ShiroRealm();
casRealm.setCachingEnabled(true);
casRealm.setAuthenticationCachingEnabled(true);
casRealm.setAuthenticationCacheName("authenticationCache");
casRealm.setAuthorizationCachingEnabled(true);
casRealm.setAuthorizationCacheName("authorizationCache");
//指定CAS服务端地址
casRealm.setCasServerUrlPrefix(casServerUrlPrefix);
//当前应用的CAS服务URL,用于接收和处理CAS服务端的Ticket
casRealm.setCasService(casService);
return casRealm;
}
@Bean
public SecurityManager securityManager(ShiroRealm casRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//ShiroRealm myRealm = new ShiroRealm();
securityManager.setRealm(casRealm);
securityManager.setSessionManager(sessionManager());
// securityManager.setCacheManager(ehCacheManager());
return securityManager;
}
@Bean
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// 设置session过期时间3600s
sessionManager.setGlobalSessionTimeout(36000000L);
return sessionManager;
}
}
4. 自定义Realm
public class ShiroRealm extends CasRealm {
/*
* 登录信息和用户验证信息验证(non-Javadoc)
* @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String)token.getPrincipal();
CasToken casToken = (CasToken) token;
if (token == null) {
return null;
}
String ticket = (String)casToken.getCredentials();
if (!StringUtils.hasText(ticket)) {
return null;
}
//由sso服务器验证ticket的合法性,获取登录信息补全
JSONObject loginInfo = loginByTicket(ticket);
//token值
String tokenVal = loginInfo.getString("data");
//登录账号
String loginId = loginInfo.getString("loginId");
Assertion casAssertion = new AssertionImpl(loginId);;
// 从CAS服务端中获取相关属性,包括用户名、是否设置RememberMe等
AttributePrincipal casPrincipal = casAssertion.getPrincipal();
String userId = casPrincipal.getName();
casToken.setUserId(userId);
Map<String, Object> attributes = casPrincipal.getAttributes();
String rememberMeAttributeName = getRememberMeAttributeName();
String rememberMeStringValue = (String)attributes.get(rememberMeAttributeName);
boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);
if (isRemembered) {
casToken.setRememberMe(true);
}
//根据业务需要将用户信息存储至session中
updateSession(loginId);
List<Object> principals = CollectionUtils.asList(userId, attributes);
PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());
return new SimpleAuthenticationInfo(principalCollection, ticket);
}
/*
* 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用,负责在应用程序中决定用户的访问控制的方法(non-Javadoc)
* @see org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
return null;
}
private JSONObject loginByTicket(String ticket){
try
{
String ssoServerUrl = getCasServerUrlPrefix();
String tokenStr = HttpUtil
.createGet(ssoServerUrl + "/sso/doLoginByTicket" +"?ticket="+ticket)
.execute()
.body();
JSONObject jsonObject = JSON.parseObject(tokenStr);
String loginId = jsonObject.getString("loginId");
return jsonObject;
}catch (Exception ex){
ex.printStackTrace();
}
return null;
}
//将用户信息存储至session,根据业务需要而定,此方法可忽略
private void updateSession(String loginId){
}
}
5. 登出接口
/**
* 用户注销
*
* @param
* @return
* @throws Exception
*/
@RequestMapping(value = "/logout")
public ModelAndView logout() throws Exception {
//获取当前登录用户名
String USERNAME = Jurisdiction.getUsername(); //当前登录的用户名
String tokenUrl = Const.WEB_APP_CONTEXT.getEnvironment().getProperty("shiro.loginUrl");
String secretkey = Const.WEB_APP_CONTEXT.getEnvironment().getProperty("sa-token.secretkey");
String logoutUrl = Const.WEB_APP_CONTEXT.getEnvironment().getProperty("shiro.logoutUrl");
//调用sa-token服务登出接口
String loginIdStr = HttpUtil
.createGet(logoutUrl +"?" + "loginId=" + USERNAME + "&secretkey=" + secretkey)
.execute()
.body();
if("ok".equals(loginIdStr)) {
mv.setView(new RedirectView(tokenUrl));
}
mv.addObject("pd", pd);
return mv;
}