簡(jiǎn)介
- 本項(xiàng)目將單點(diǎn)登錄和權(quán)限認(rèn)證結(jié)合在一起挂捅,實(shí)現(xiàn)了用戶登錄與授權(quán)的基礎(chǔ)框架包竹,后續(xù)可以很好的在此框架上進(jìn)行二次開(kāi)發(fā)
- 在原理和配置上也有很多不清楚的地方,希望大家留言討論~
- 后續(xù)將會(huì)進(jìn)行spring cloud系列工程的搭建籍凝,并將shiro-cas嘗試接入spring cloud中
- spring cloud系列直達(dá)地址 spring cloud
單點(diǎn)登錄流程
- 客戶端請(qǐng)求目標(biāo)服務(wù)器
- 目標(biāo)服務(wù)器重定向到cas服務(wù)器
- cas服務(wù)器進(jìn)行驗(yàn)證,通過(guò)則請(qǐng)求目標(biāo)服務(wù)器苗缩,將ticket傳給目標(biāo)服務(wù)器
- 目標(biāo)服務(wù)器根據(jù)ticket饵蒂,請(qǐng)求cas服務(wù)器,獲取用戶登錄信息
- cas服務(wù)器返回驗(yàn)證消息給目標(biāo)服務(wù)器
項(xiàng)目實(shí)現(xiàn)
1酱讶、導(dǎo)入依賴包
<!--Apache Shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-cas</artifactId>
<version>${shiro.version}</version>
</dependency>
2退盯、 編輯配置文件
# shiro - cas 配置
shiro:
# 在訪問(wèn)cas服務(wù)器登錄之后,會(huì)返回一個(gè)ticket泻肯。由該地址接收
casFilterUrlPattern: /shiro-cas
# cas服務(wù)前綴
casServerUrlPrefix: http://127.0.0.1:8181/cas
# shiro服務(wù)前綴
shiroServerUrlPrefix: http://127.0.0.1:${server.port}${server.servlet.context-path}
# 登錄地址
loginUrl: ${shiro.casServerUrlPrefix}/login?service=${shiro.shiroServerUrlPrefix}${shiro.casFilterUrlPattern}
# 登出地址
logoutUrl: ${shiro.casServerUrlPrefix}/logout?service=${shiro.shiroServerUrlPrefix}${shiro.casFilterUrlPattern}
# Tomcat
server:
tomcat:
uri-encoding: UTF-8
max-threads: 1000
min-spare-threads: 30
port: 40301
connection-timeout: 5000ms
servlet:
context-path: /shiro
session:
cookie:
http-only: true
spring:
application:
name: service-auth
# shiro - cas 配置
shiro:
# 在訪問(wèn)cas服務(wù)器登錄之后渊迁,會(huì)返回一個(gè)ticket。由該地址接收
casFilterUrlPattern: /shiro-cas
# cas服務(wù)前綴
casServerUrlPrefix: http://127.0.0.1:8181/cas
# shiro服務(wù)前綴
shiroServerUrlPrefix: http://127.0.0.1:${server.port}${server.servlet.context-path}
# 登錄地址
loginUrl: ${shiro.casServerUrlPrefix}/login?service=${shiro.shiroServerUrlPrefix}${shiro.casFilterUrlPattern}
# 登出地址
logoutUrl: ${shiro.casServerUrlPrefix}/logout?service=${shiro.shiroServerUrlPrefix}${shiro.casFilterUrlPattern}
3灶挟、自定義配置casFileter
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.cas.CasFilter;
import org.apache.shiro.cas.CasToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
/**
* @author : xingsongtan@qq.com
* @date : 14:17 2019/7/17
*/
public class MyCasFilter extends CasFilter {
private static Logger logger = LoggerFactory.getLogger(MyCasFilter.class);
private static final String TICKET_PARAMETER = "ticket";
public MyCasFilter() {
}
@Override
public AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
// 獲取請(qǐng)求的ticket
HttpServletRequest httpRequest = (HttpServletRequest) request;
String ticket = getRequestTicket(httpRequest);
if (StringUtils.isEmpty(ticket)) {
logger.debug("票證獲取失敗,票證為空琉朽!");
return null;
}
return new CasToken(ticket);
}
/**
* 拒絕除了option以外的所有請(qǐng)求
**/
@Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) {
return true;
}
return false;
}
@Override
public boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
// 獲取ticket,如果不存在稚铣,直接返回false
String ticket = getRequestTicket((HttpServletRequest) request);
if (StringUtils.isEmpty(ticket)) {
return false;
}
return this.executeLogin(request, response);
}
/**
* 獲取請(qǐng)求的ticket
*/
private String getRequestTicket(HttpServletRequest httpRequest) {
// 從參數(shù)中獲取ticket
String ticket = httpRequest.getParameter(TICKET_PARAMETER);
if (StringUtils.isEmpty(ticket)) {
// 如果為空的話箱叁,則從header中獲取參數(shù)
ticket = httpRequest.getHeader(TICKET_PARAMETER);
}
return ticket;
}
}
4、自定義casRealm
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cas.CasRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.Set;
/**
* @author : xingsongtan@qq.com
* @date : 12:00 2019/7/17
*/
public class MyCasRealm extends CasRealm {
private static Logger log = LoggerFactory.getLogger(MyCasRealm.class);
/**
* 在調(diào)用subject.login()時(shí)惕医,首先調(diào)用此接口
*/
@Override
public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 調(diào)用父類的方法耕漱,然后授權(quán)用戶
AuthenticationInfo authc = super.doGetAuthenticationInfo(token);
// 獲得用戶名
String username = (String) authc.getPrincipals().getPrimaryPrincipal();
// TODO:這里應(yīng)該從數(shù)據(jù)庫(kù)中獲取用戶信息
return authc;
}
/**
* 進(jìn)行權(quán)限驗(yàn)證的時(shí)候,調(diào)用方法抬伺,將用戶的權(quán)限信息寫進(jìn)SimpleAuthorizationInfo
*/
@Override
public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 用戶名稱
log.info("進(jìn)入了權(quán)限認(rèn)證");
Object username = principals.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// TODO: 這里應(yīng)該從數(shù)據(jù)庫(kù)獲取用戶權(quán)限
Set<String> permission = new HashSet<>();
permission.add("sys:dept:list");
info.setStringPermissions(permission);
return info;
}
}
6螟够、進(jìn)行shiroconfig配置
import com.ttcode.shiro.security.MyCasFilter;
import com.ttcode.shiro.security.MyCasRealm;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.cas.CasSubjectFactory;
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.filter.authc.LogoutFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
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.web.filter.DelegatingFilterProxy;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Shiro的配置文件
*
* @author : xingsongtan@qq.com
* @date : 20:51 2019/7/18
*/
@Configuration
public class ShiroCasConfiguration {
/**
* 添加shiro的filter
*/
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
filterRegistration.addInitParameter("targetFilterLifecycle", "true");
filterRegistration.setEnabled(true);
filterRegistration.addUrlPatterns("/*");
return filterRegistration;
}
/**
* 保證了shiro內(nèi)部lifecycle函數(shù)bean的執(zhí)行
*/
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 配置授權(quán)策略
*/
@Bean(name = "authenticator")
public ModularRealmAuthenticator modularRealmAuthenticator() {
ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
return authenticator;
}
@Bean(name = "casRealm")
public MyCasRealm casRealm(@Value("${shiro.casServerUrlPrefix}") String casServerUrlPrefix,
@Value("${shiro.shiroServerUrlPrefix}") String shiroServerUrlPrefix,
@Value("${shiro.casFilterUrlPattern}") String casFilterUrlPattern) {
MyCasRealm casRealm = new MyCasRealm();
// 認(rèn)證通過(guò)后的默認(rèn)角色
casRealm.setDefaultRoles("ROLE_USER");
// cas服務(wù)端地址前綴
casRealm.setCasServerUrlPrefix(casServerUrlPrefix);
// 應(yīng)用服務(wù)地址,用來(lái)接收cas服務(wù)端票證
casRealm.setCasService(shiroServerUrlPrefix + casFilterUrlPattern);
return casRealm;
}
/**
* 配置安全管理器
**/
@Bean(name = "securityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(ModularRealmAuthenticator authenticator,
MyCasRealm casRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 設(shè)置授權(quán)策略,此步驟必須在設(shè)置realm的前面,不然會(huì)報(bào)錯(cuò)realm未配置
securityManager.setAuthenticator(authenticator);
securityManager.setSubjectFactory(new CasSubjectFactory());
// 緩存管理器
securityManager.setCacheManager(new MemoryConstrainedCacheManager());
// 設(shè)置自定義驗(yàn)證策略
securityManager.setRealm(casRealm);
return securityManager;
}
/**
* 配置登錄過(guò)濾器
*/
@Bean(name = "casFilter")
public MyCasFilter casFilter(@Value("${shiro.loginUrl}") String loginUrl) {
MyCasFilter casFilter = new MyCasFilter();
casFilter.setName("casFilter");
casFilter.setEnabled(true);
casFilter.setFailureUrl(loginUrl);
return casFilter;
}
/**
* shiro 過(guò)濾器
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager,
MyCasFilter casFilter,
@Value("${shiro.logoutUrl}") String logoutUrl,
@Value("${shiro.loginUrl}") String loginUrl,
@Value("${shiro.casFilterUrlPattern}") String casFilterUrlPattern) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 設(shè)置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 設(shè)置登錄地址
shiroFilterFactoryBean.setLoginUrl(loginUrl);
// 設(shè)置登錄成功地址
shiroFilterFactoryBean.setSuccessUrl("/");
// 配置攔截地址
Map<String, Filter> filters = new HashMap<>();
filters.put("casFilter", casFilter);
LogoutFilter logoutFilter = new LogoutFilter();
// 配置登出地址
logoutFilter.setRedirectUrl(logoutUrl);
filters.put("logout", logoutFilter);
shiroFilterFactoryBean.setFilters(filters);
// 設(shè)置訪問(wèn)用戶頁(yè)面需要授權(quán)的操作
loadShiroFilterChain(shiroFilterFactoryBean, casFilterUrlPattern);
// 將設(shè)置的權(quán)限設(shè)置到shiroFilterFactoryBean
return shiroFilterFactoryBean;
}
/**
* 1妓笙、當(dāng)我們第一次訪問(wèn)客戶端時(shí)若河,先去cas進(jìn)行認(rèn)證,成功后會(huì)返回一個(gè)ticket
* 2给郊、返回的ticket地址在casRealm已經(jīng)進(jìn)行了配置牡肉,shiroServerUrlPrefix + casFilterUrlPattern
* 3、即地址為/shiro-cas淆九,對(duì)該地址進(jìn)行casFilter攔截
*/
private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean, String casFilterUrlPattern) {
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put(casFilterUrlPattern, "casFilter");
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
}
/**
* 開(kāi)啟Shiro的注解(如@RequiresPermissions)
* 需借助SpringAOP掃描使用Shiro注解的類
* 配置以下兩個(gè)bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)即可實(shí)現(xiàn)此功能
*
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/**
* 開(kāi)啟aop注解支持
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
結(jié)尾:以上為核心配置