Некоторые картинки непонятны, GitHub содержит:GitHub.com/nice01, а затем /is…Ресурсы GitHub были загружены
Введение Широ (личное понимание)
Shiro — это фреймворк для управления разрешениями пользователей. Он используется для управления и проверки ролей пользователей и разрешений, которые имеет каждая роль.Функция очень проста, но Широ превратил ее в очень расширяемую структуру, которая является спецификацией для проверки и аутентификации.Конкретной реализацией является конфигурация. Широ.
Ниже мой процесс чтения, и некоторые небольшие резюме, и исходный код может быть запущен. Если shiro сравнить с самураем, SecurityManager — это самурайский меч, а shiro Filter — это человек-самурай, один — инструмент, а другой — пользователь. Видно, что эти два аспекта очень важны. В этой статье дается объяснение этих двух аспектов и процесс чтения исходного кода. Написание немного грубое, но если вы хотите прочитать исходный код shiro, GitHub имеет настроенный исходный код и готовый пример Springboot и симуляция единого входа.
Если вы внимательно изучите это, вы получите следующие навыки:
- Все конфигурации, связанные с сиро (например,
springmvc
как настроить широ,springboot
Как настроить Широ, как настроить единый вход......) - Лучшее понимание системы управления правами
- Углубите свое понимание самой настройки фреймворка
Общее введение проекта:
Весь проект клонирован с ШироОфициальный склад, только в подпроектеsamples
Добавлено 2 небольших проекта для отладки.
-
shiro_learn(корневой путь проекта)
- samples
- shiro_cas_service использует springboot для простого моделирования службы cas (вы можете полностью понять процесс запроса службы cas через отладку)
- shiro_client — это место, где начинается анализ исходного кода, большая часть конфигурации shiro настроена.
- samples
Эта статья будет анализировать с трех аспектов:
- конфигурация широ (очень полная)
- Процесс загрузки веб-фильтра Shiro
- Полный анализ запроса shiro cas
конфигурация Широ
На картинке выше показаны все функции широ.Из картинки видно, что она в основном разделена на 2 больших блока.
КусокSecurity Manager
, этот модуль и проверка авторизации в одном (соответствующий класс:SecurityManager
subclass), и предоставлять внешние услуги через интерфейс Subject, такие как вход в систему, аутентификация и т. д., просто используйте тему (конкретное использование будет подробно проанализировано позже);
Другая часть — это пользователь, т.SecurityManager
Он был настроен. Как третья сторона должна его использовать? Эта статья начинается сWeb MVC
Эта третья сторона рассказывает,Web MVC
В основном блокировать все пользовательские запросы через фильтр Filter, а затем распределять их в соответствующем широ-фильтре для соответствующей обработки (например, посмотреть, зарегистрирован ли запрос и имеет ли он разрешение), и использовать его в фильтреSecurityManager
который предоставилSubject
интерфейс для выполнения соответствующих операций.
SecurityManager
настроить
На приведенном выше рисунке мы видим, что свойства, которые может настроить DefaultSecurityManager, можно увидеть.В следующей таблице они объясняются одно за другим:
Атрибуты | Есть ли конфигурация по умолчанию | Класс конфигурации по умолчанию | Описание функции | Общая конфигурация |
---|---|---|---|---|
subjectFactory | да | DefaultSubjectFactory | Конкретный класс реализации Subject (интерфейс внешней службы, если это служба cas, рекомендуется использоватьCasSubjectFactoryпокрытие) | нет |
subjectDAO | да | DefaultSubjectDAO | В основном используется для сохранения последней информации в теме к сеансу | нет |
rememberMeManager | Существует только в DefaultWebSecurityManager | CookieRememberMeManager | Используется для управления куки-файлом RememberMe, обычно не используется | нет |
sessionManager | да | DefaultSessionManager (DefaultWebSecurityManager — это DefaultWebSessionManager) | Операции, связанные с сеансом, в конечном итоге будут делегированы ему (это также можно настроить, см. таблицу ниже) | да |
authorizer | да | ModularRealmAuthorizer | Стратегия авторизации (вы можете установить собственную стратегию при наличии нескольких областей) | нет |
authenticator | да | ModularRealmAuthenticator | Стратегия аутентификации (вы можете установить свою собственную стратегию при наличии нескольких областей) | да |
realm | нет | CasRealm, JdbcRealm... | Для реализации операций аутентификации и авторизации нужно настроить самостоятельно (пример будет позже) | да |
cacheManager | нет | Пользователям лучше всего наследовать абстрактный класс AbstractCacheManager (поддерживает управление циклами по умолчанию shiro). | Он будет использоваться при аутентификации и авторизации области (эквивалентно добавлению уровня кеша, аутентификация cas не требуется, если это имя пользователя и пароль, его можно использовать для увеличения скорости аутентификации и авторизации) | Нет (не требуется для cas) |
Конфигурация свойства sessionManager (DefaultSessionManager) DefaultSecurityManager:
Атрибуты | Есть ли конфигурация по умолчанию | Класс конфигурации по умолчанию | Описание функции | Общая конфигурация |
---|---|---|---|---|
sessionFactory | да | SimpleSessionFactory | Используется для создания сеанса, обычно не настраивается | нет |
sessionDAO | да | MemorySessionDAO | Интерфейс, используемый для сохранения сеанса, как правило, посредством наследованияAbstractSessionDAOкласс и использовать Redis для перенастройки одного из них для сохранения сеанса в Redis (этот абстрактный класс может настроить свой собственныйSessionIdGenerator=> используется для генерации идентификатора сеанса) | да |
casheManager | нет | Его можно использовать в сочетании с sessionDAO, который реализует класс CashManagerAware.Как правило, sessionDAO настраивается. | нет | |
sessionIdCookie | да | new SimpleCookie("JSESSIONID"); | Он существует в DefaultWebSessionManager, подклассе этого класса, и обычно реконфигурируется так, чтобы имя файла cookie можно было изменить на свое собственное (как правило, ему делегируются базовые операции, связанные с файлами cookie, такие как чтение значения этого файла cookie, настройка печенье.. ....) | да |
Примечание. Эти свойства можно установить с помощью метода set объекта.
Web MVC
конфигурация фильтра
DefaultWebSecurityManager аналогичен конфигурации DefaultSecurityManager, следующий securityManager относится к DefaultWebSecurityManager.
Теперь, когда DefaultWebSecurityManager настроен, куда эту штуку ставить, надо применить к широ фильтру.
Конфигурация, связанная с фильтром Широ, хранится вShiroFilterFactoryBean
внутри этого класса, а затем используйтеDelegatingFilterProxy
будетShiroFilterFactoryBean
Внедряется в веб-контейнер.
- Если это springmvc, настройте его непосредственно в web.xml.
DelegatingFilterProxy
Ну, имя фильтраShiroFilterFactoryBean
имя этого компонента; - Если это springboot, используйте springboot
FilterRegistrationBean
Просто зарегистрируйтесь (servlet3.0 можно зарегистрировать с помощью ServletContext, сервлет, фильтр можно зарегистрировать...) =>я расскажу об этом позже
ShiroFilterFactoryBean
, Как вы можете видеть из имени, он не является реальным фильтром, он использует его настроить и управлять Shirofilter.
Как видно из рисунка выше, возвращайте Springshirofilter этот объект через FactoryBean.ShiroFilterFactoryBean
Инструкции по настройке следующие:
Атрибуты | Описание функции |
---|---|
securityManager |
широ ядро...... |
filters (Map<String, Filter>) |
Поместите все пользовательские фильтры широ на эту карту, обычно используемые в сочетании со следующим определением. |
filterChainDefinitionMap (Map<String, String>) |
Map (исходное объяснение: urlPathExpression_to_comma-delimited-filter-chain-definition) |
loginUrl |
URL-адрес входа |
successUrl |
URL для перехода после успешного входа в систему |
unauthorizedUrl |
URL, на который неудачно перенаправляется авторизация |
Все приведенные выше примеры конфигурации размещены в shiro_learn/samples/shiro_clien/src/main/java/com/nice01qc/config/shiro/ShiroCasConfig.java
В этом классе вы можете посмотреть на это соответственно.
Процесс загрузки веб-фильтра Shiro
Этот процесс загрузки на самом деле является процессом загрузки ShiroFilterFactoryBean.
Теперь securityManager был настроен вручную, нечего сказать. давайте кодировать ......., все в источнике указали, что более критические узлы, детали его реализации.
Процесс загрузки ShiroFilterFactoryBean.java
public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor {
// 就是从这个地方开始(如果不知道,请搜索 FactoryBean的作用)
public Object getObject() throws Exception {
if (instance == null) {
instance = createInstance(); //直接看这个就好
}
return instance;
}
protected AbstractShiroFilter createInstance() throws Exception {
SecurityManager securityManager = getSecurityManager();
// 封装Filter调用管理,继续深入
FilterChainManager manager = createFilterChainManager();
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
chainResolver.setFilterChainManager(manager);
//此处创建真正的 总览全局的 shiro filter
return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}
protected FilterChainManager createFilterChainManager() {
DefaultFilterChainManager manager = new DefaultFilterChainManager();
// 获取默认Filter ,取自DefaultFilter枚举类(共12个)
Map<String, Filter> defaultFilters = manager.getFilters();
// 将loginUrl、successUrl、unauthorizedUrl填充到符合要求的 filter 内
for (Filter filter : defaultFilters.values()) {
applyGlobalPropertiesIfNecessary(filter);
}
//这是你自己定义的Filter
Map<String, Filter> filters = getFilters();
if (!CollectionUtils.isEmpty(filters)) {
for (Map.Entry<String, Filter> entry : filters.entrySet()) {
String name = entry.getKey();
Filter filter = entry.getValue();
applyGlobalPropertiesIfNecessary(filter); // 填充一波
if (filter instanceof Nameable) {
((Nameable) filter).setName(name);
}
//'init' argument is false, since Spring-configured filters should be initialized
//in Spring (i.e. 'init-method=blah') or implement InitializingBean:
manager.addFilter(name, filter, false);
}
}
//build up the chains:
Map<String, String> chains = getFilterChainDefinitionMap(); // filterChainDefinitionMap
if (!CollectionUtils.isEmpty(chains)) {
for (Map.Entry<String, String> entry : chains.entrySet()) {
// 例如 filterChainDefinitionMap.put("/index", "authc[config1,config2]");
String url = entry.getKey(); // url 就是 "/index"
String chainDefinition = entry.getValue(); // "authc[config1,config2]"
// 会将 chainDefinition的“[config1,config2]” 解析后封装在 authc对应的filter内部,后续会用到
manager.createChain(url, chainDefinition);
}
}
return manager;
}
// 填充那三个值的地方
private void applyGlobalPropertiesIfNecessary(Filter filter) {
applyLoginUrlIfNecessary(filter);
applySuccessUrlIfNecessary(filter);
applyUnauthorizedUrlIfNecessary(filter);
}
private void applyLoginUrlIfNecessary(Filter filter) {
String loginUrl = getLoginUrl();
if (StringUtils.hasText(loginUrl) && (filter instanceof AccessControlFilter)) {
AccessControlFilter acFilter = (AccessControlFilter) filter;
String existingLoginUrl = acFilter.getLoginUrl();
if (AccessControlFilter.DEFAULT_LOGIN_URL.equals(existingLoginUrl)) {
acFilter.setLoginUrl(loginUrl);
}
}
}
}
Выше приведен процесс инициализации класса ShiroFilterFactoryBean.DelegatingFilterProxy
Получите этот bean-компонент через getBean("имя bean-компонента ShiroFilterFactoryBean") и делегируйте ему запрос.
shiro Filter
Система наследования в деталях
Прежде чем идти дальше, давайте сначала представим характеристики фильтра широ, то есть вы хотите реализовать фильтры с разными функциями, просто наследуйте фильтр, который идет в комплекте с широ, а затем вносите модификации на этой основе.
Каждый из вышеперечисленных абстрактных классов имеет разные функции, и разделение труда ясно.Давайте проанализируем один за другим (приведенный выше loginUrl был введен, когда он был инициализирован выше): => Это очень важно
AbstractFilter.java
// 实现了Filter的init接口,并对外暴露了onFilterConfigSet 接口
public abstract class AbstractFilter extends ServletContextSupport implements Filter {
// 实现了Filter的init接口,并对外暴露了onFilterConfigSet 接口,这个接口在AbstractShiroFilter中覆盖了这个方法,其中AbstractShiroFilter 是 SpringShiroFilter的父类喔,SpringShiroFilter的父类喔,SpringShiroFilter的父类喔
public final void init(FilterConfig filterConfig) throws ServletException {
setFilterConfig(filterConfig);
try {
onFilterConfigSet();
} catch (Exception e) {
}
}
public void destroy() {
}
}
NameableFilter.java
public abstract class NameableFilter extends AbstractFilter implements Nameable {
// 设置filter 名字用的
public void setName(String name) {
this.name = name;
}
}
OncePerRequestFilter.java
public abstract class OncePerRequestFilter extends NameableFilter {
// 保证每次请求只访问一次
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 查看是否已经访问过一次
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
// 如果访问了一次,则跳过这个filter,继续下一个
if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
filterChain.doFilter(request, response);
} else if (!isEnabled(request, response) || shouldNotFilter(request) ) {
filterChain.doFilter(request, response);
} else {
// 没有访问,现在标记
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
// 执行本filter 内容
doFilterInternal(request, response, filterChain);
} finally {
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}
// 子类需要实现的接口,不需要实现doFilter这个方法,通过实现这个方法,可以干更多事
protected abstract void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException;
}
AdviceFilter.java
public abstract class AdviceFilter extends OncePerRequestFilter {
// 实现了OncePerRequestFilter这个方法,在这个方法有点像aop的风格
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
try {
// 在执行之前,先执行 preHandle 方法
boolean continueChain = preHandle(request, response);
// 如果 preHandle 没通过,将不再继续往下执行
if (continueChain) {
executeChain(request, response, chain);
}
// 执行之后再执行的方法
postHandle(request, response);
} catch (Exception e) {
exception = e;
} finally {
cleanup(request, response, exception);
}
}
// 将这个方法暴露出去
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
return true;
}
// 将这个方法也暴露出去
protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {
}
}
PathMatchingFilter.java
// 用于判断请求是否符合本 Filter,只有请求URL 跟本Filter对应的url匹配规则对上
public abstract class PathMatchingFilter extends AdviceFilter implements PathConfigProcessor {
// ShiroFilterFactoryBean 初始化时就填充进去了,
// 里面value就是那个autho[config1,config2] 中这个[config1,config2]数组
protected Map<String, Object> appliedPaths = new LinkedHashMap<String, Object>();
// 覆盖了父类的preHandle方法,首先判断请求url是否匹配 本Filter的appliedPaths
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
// 如果Filter本身没有匹配url,返回true
if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {
return true;
}
for (String path : this.appliedPaths.keySet()) {
//(first match 'wins'):
if (pathsMatch(path, request)) {
Object config = this.appliedPaths.get(path);
// 如果匹配上了就执行这个方法,它会进一步交给onPreHandle方法,并对外暴露这个方法
return isFilterChainContinued(request, response, path, config);
}
}
//no path matched, allow the request to go through:
return true;
}
private boolean isFilterChainContinued(ServletRequest request, ServletResponse response,
String path, Object pathConfig) throws Exception {
if (isEnabled(request, response, path, pathConfig)) { //isEnabled check added in 1.2
return onPreHandle(request, response, pathConfig);
}
return true;
}
// 对外暴露此接口
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return true;
}
}
AccessControlFilter.java
(Если вы хотите проверить авторизацию или что-то в этом роде, этот класс все же более критичен)
// 此接口用于判断请求是否可以通过,不通过就跳登录,符合要求就使用subject进行登录 等等
public abstract class AccessControlFilter extends PathMatchingFilter {
// 覆盖父类PathMatchingFilter 暴露的方法,并在方法内添加了两种方法
// 一个是isAccessAllowed,用于判断是否已经验证过了,例如用户已经登录过了有session
// 另一个是 onAccessDenied 验证失败,失败后干嘛,鬼知道,你看他怎么实现的
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
// 直接暴露给外界,自己看着实现吧,可别把onAccessDenied的活也干了就好了
protected abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;
// 这个老哥,提供了两个方式供外界覆盖,也就是方法 重载,就是看你要不要那个参数
protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return onAccessDenied(request, response);
}
// .......
protected abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception;
// 判断是不是登录请求
protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {
return pathsMatch(getLoginUrl(), request);
}
// 保存请求并重定向到登录页面
protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
saveRequest(request);
redirectToLogin(request, response);
}
protected void saveRequest(ServletRequest request) {
WebUtils.saveRequest(request);
}
protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
String loginUrl = getLoginUrl();
WebUtils.issueRedirect(request, response, loginUrl);
}
}
Кратко о вышеизложенном:
- Если вы просто хотите написать простой фильтр, реализуйте интерфейс фильтра напрямую.
- Если вы хотите дать фильтру имя, наследуйте абстрактный класс NameableFilter.
- Если вы хотите, чтобы ваш фильтр вызывался только один раз, расширьте абстрактный класс OncePerRequestFilter.
- Если вы хотите, чтобы ваш фильтр был до и после метода dofilter (аналогично aop этого метода), наследуйте AdviceFilter (посмотрите, очень ли вам знаком этот совет ----- spring aop также имеет концепцию совета)
- Если вы хотите написать фильтр для аутентификации и авторизации, пожалуйста, продолжайте читать, потому что есть два класса, которые реализуют AccessControlFilter (колесо построено, садитесь в машину)
Один - аутентификация:
AuthenticationFilter.java
(Слова, которые обычно заканчиваются на ion, обычно предоставляют очень простые услуги)
public abstract class AuthenticationFilter extends AccessControlFilter {
// 提供基础的验证
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
Subject subject = getSubject(request, response);
return subject.isAuthenticated() && subject.getPrincipal() != null;
}
protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {
// 通过验证后,会被重定向到自己设定好的url,这个可以改
WebUtils.redirectToSavedRequest(request, response, getSuccessUrl());
}
}
AuthenticatingFilter.java
(Для тех, кто реально работает, можно будет в будущем заверить наследство, и можно будет немного его доработать)
public abstract class AuthenticatingFilter extends AuthenticationFilter {
// 覆盖了,父类的方法,并在这个方法内部添加了vip功能(可以使用isPermissive走vip通道)
// 如果是 AuthorizationFilter 的这个方法,mappedValue就是用于角色验证了,默认是所有角色都必须通过,可以覆盖这个方法
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return super.isAccessAllowed(request, response, mappedValue) ||
(!isLoginRequest(request, response) && isPermissive(mappedValue));
}
// 这是vip模板,仅仅是vip,普通乘客就别走这个了
protected boolean isPermissive(Object mappedValue) {
if(mappedValue != null) {
String[] values = (String[]) mappedValue;
return Arrays.binarySearch(values, PERMISSIVE) >= 0;
}
return false;
}
// 这个方法一般提供给onAccessDenied 方法的,就是你没通过认证,应该登录认证一波了
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
AuthenticationToken token = createToken(request, response);
if (token == null) {
throw new IllegalStateException(msg);
}
try {
Subject subject = getSubject(request, response);
subject.login(token);
return onLoginSuccess(token, subject, request, response);
} catch (AuthenticationException e) {
return onLoginFailure(token, e, request, response);
}
}
// 对外暴露接口,用于生成token,因为登录必须拿着token去登录,默认提供了两种生成token的方法,就在这个方法下面喔
protected abstract AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception;
}
**Последний пример: **
Одинshiro Filter
Пример введения
CasFilter.java(посмотрим, что сделал Кас)
public class CasFilter extends AuthenticatingFilter {
// 直接覆盖父类这个方法,意思就是,你竟然遇到了我,那你就是没有认证(需要被安排下)
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return false;
}
// 妥妥的实现这个方法,带我去登录吧,我准备好了
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String ticket = httpRequest.getParameter(TICKET_PARAMETER);
return new CasToken(ticket);
}
// 去吧皮卡丘,送你去登录
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
// 所以casFilter 直接执行这一步,前面那些方法都没起作用
return executeLogin(request, response);
}
}
Не говоря уже о классе авторизации (тот же, что и выше), ниже приведены некоторые реализации вышеупомянутого фильтра по умолчанию (можно увидеть в классе перечисления DefaultFilter) (знакомо?)
имя фильтра | Соответствующий фильтр |
---|---|
anon |
AnonymousFilter.java |
authc |
FormAuthenticationFilter.java |
authcBasic |
BasicHttpAuthenticationFilter.java |
authcBearer |
BearerHttpAuthenticationFilter.java |
logout |
LogoutFilter.java |
noSessionCreation |
NoSessionCreationFilter.java |
perms |
PermissionsAuthorizationFilter.java |
port |
PortFilter.java |
rest |
HttpMethodPermissionFilter.java |
roles |
RolesAuthorizationFilter.java |
ssl |
SslFilter.java |
user |
UserFilter.java |
Благодаря пониманию вышеуказанного фильтра, вы уже поняли фильтр Широ? Если вы не понимаете его, пожалуйста, прочитайте его еще раз (это суждение бесконечного цикла, если вы его не поняли, оно не сломается;) Контейнер инициализация завершена, пришло время сделать запрос.
Полный анализ запроса широ
Начнем с того места, где запрос перехватывается Фильтром.Кто его перехватывает?Это класс DelegatingFilterProxy.Ну и сделаем брейкпоинт на методе doFilter этого класса (перед брейкпойнтом посмотрим на init( ) способ) начнем......
DelegatingFilterProxy.java (где начинается запрос)
public class DelegatingFilterProxy extends GenericFilterBean {
// 这个方法是在Filter init时调用的,在启动服务过程就会调用,他会初始化delegate,而这个delegate就是ShiroFilterFactoryBean
@Override
protected void initFilterBean() throws ServletException {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
// If no target bean name specified, use filter name.
// 什么是targetBeanName 见 DelegatingFilterProxy构造函数
if (this.targetBeanName == null) {
this.targetBeanName = getFilterName();
}
WebApplicationContext wac = findWebApplicationContext();
if (wac != null) {
this.delegate = initDelegate(wac); // 直接看这个
}
}
}
}
// 看这个就好了
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
String targetBeanName = getTargetBeanName();
// 直接通过ApplicationContex直接getBean了
Filter delegate = wac.getBean(targetBeanName, Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
// targetBeanName的由来
public DelegatingFilterProxy(String targetBeanName, @Nullable WebApplicationContext wac) {
this.setTargetBeanName(targetBeanName);
this.webApplicationContext = wac;
if (wac != null) {
this.setEnvironment(wac.getEnvironment());
}
}
// doFilter 在这里,来吧!!! 在这里,来吧!!! 在这里,来吧!!!
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// Lazily initialize the delegate if necessary.
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized (this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = findWebApplicationContext();
delegateToUse = initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
// Let the delegate perform the actual doFilter operation.
// 直接去这个方法看吧
invokeDelegate(delegateToUse, request, response, filterChain);
}
// 啥也不干,直接就抛给了ShiroFilterFactoryBean的SpringShiroFilter这个内部类
protected void invokeDelegate(
Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
delegate.doFilter(request, response, filterChain);
}
}
SpringShiroFilter
ShiroFilterFactoryBean$SpringShiroFilter
родительский классOncePerRequestFilter.java
(СледующееSpringShiroFilter
миссия)
public abstract class OncePerRequestFilter extends NameableFilter {
// 跳到这里来了
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) {
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
filterChain.doFilter(request, response);
} else if (!isEnabled(request, response) || shouldNotFilter(request) ) {
filterChain.doFilter(request, response);
} else {
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
// 直接看看覆盖了这个方法的类吧
doFilterInternal(request, response, filterChain);
} finally {
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}
}
SpringShiroFilter
родительский классAbstractShiroFilter.java
public abstract class AbstractShiroFilter extends OncePerRequestFilter {
// 此处是真正的实现
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)throws ServletException, IOException {
try {
final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
// 在此处创建了 subject喔,记住了喔!!!
final Subject subject = createSubject(request, response);
//noinspection unchecked
// subject创建好,并将下面两个方法封装在Callable()里面再执行,
// 在执行这个call之前,先将subject绑定到当前线程,执行完后,清理当前线程的绑定
// 为什么非要搞个Callable,直接在 这两个方法前后放两个方法就好了,可能是因为这样扩展性更强
// 以后在外面再封装一层也方便,说不定还可以搞异步???
subject.execute(new Callable() {
public Object call() throws Exception {
// 更新session时间
updateSessionLastAccessTime(request, response);
//************************************************
// 执行shiro过滤链,(先讲subject创建过程吧,后续再讲)
//************************************************
executeChain(request, response, chain);
return null;
}
});
} catch (ExecutionException ex) {
t = ex.getCause();
}
}
//*****************************************
// 暂时记这个创建过程为subject创建过程
//*****************************************
// subjcet 创建过程交给了父类Subject$Builder了,并送了他一个securityManager
protected WebSubject createSubject(ServletRequest request, ServletResponse response){
return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
}
}
//==============================================================>
// 简单展示,具体自己debug看看
public class SubjectCallable<V> implements Callable<V> {
public V call() throws Exception {
try {
// 绑定到当前线程
threadState.bind();
// 执行自己实现的那个call方法
return doCall(this.callable);
} finally {
// 清除数据
threadState.restore();
}
}
}
процесс создания темы
Subject$Builder
(Subject
внутренний статический классBuild
)
public interface Subject {
public static class Builder {
// 从这儿可以看出,最终委托给了SecurityManager来干这个
public Subject buildSubject() {
return this.securityManager.createSubject(this.subjectContext);
}
}
}
DefaultSecurityManager.java
(Следующее обсуждение относится к этому классу и системе родительских классов этого класса)
public class DefaultSecurityManager extends SessionsSecurityManager {
// 在subjcetContext基础上重新new一个喔,不影响前面的subjectContext
public Subject createSubject(SubjectContext subjectContext) {
//create a copy so we don't modify the argument's backing map:
// 就是一个Map,里面存储了很多验证过程的东西,例如是否认证,是否是remember等等,
// 在DefaultSubjectContext类里面可以看到,有:securityManager,sessionId,authenticationToken,authenticationInfo,subject,principals,session,authenticated,host,sessionCreationEnabled,principalsSessionKey,authenticatedSessionKey
// 就是一个临时状态和工具集合地,如果是DefaultWebSecurityManager就创建一个
// DefaultWebSubjectContext实例,实际上本文讨论的就是web
SubjectContext context = copy(subjectContext);
//确保SecurityManager 已经放到context里面去了
context = ensureSecurityManager(context);
// 这个最关键,也是最复杂一个,也不复杂,就深入了几个类,这也是下面要讲的重点
// step0.........
context = resolveSession(context);
// 这个嘛,等会儿说
context = resolvePrincipals(context);
// 前戏已经准备好了,改开始创建Subject了
Subject subject = doCreateSubject(context);
// 这个会把当前Subject中最新的信息同步到session里面,还有其他功能,后续可以深入看看
save(subject);
return subject;
}
// step1,去解决session
protected SubjectContext resolveSession(SubjectContext context) {
if (context.resolveSession() != null) {
return context;
}
try {
// 直接看resolveContextSession
Session session = resolveContextSession(context);
if (session != null) {
context.setSession(session);
}
} catch (InvalidSessionException e) {
}
return context;
}
// step2,开始了
protected Session resolveContextSession(SubjectContext context) throws InvalidSessionException {
// 先看看context里面有没有,有的话就不用继续找了,省时间,没有的话,就将request和response包装到SessionKey里面
SessionKey key = getSessionKey(context);
if (key != null) {
// 现在真正开始了,但这事得分工,直接交给专门管理session的父类 SessionsSecurityManager(代码里面的啃老族,把活全交给父类干,很正常喔)
return getSession(key);
}
return null;
}
}
SessionsSecurityManager.java
(Все операции, связанные с сеансом, управляются им)
public abstract class SessionsSecurityManager extends AuthorizingSecurityManager {
// step3. 看到这里,发现SecurityManager整体是不会干活的,就管着整个流程,然后分发出去
public Session getSession(SessionKey key) throws SessionException {
// 这个sessionManger,如果你不覆盖它,默认就是DefaultSessionManager这个类,我们就从这个开始吧
return this.sessionManager.getSession(key);
}
}
AbstractNativeSessionManager.java
(DefaultSessionManager
отец)
public abstract class AbstractNativeSessionManager extends AbstractSessionManager implements NativeSessionManager, EventBusAware {
// step4. 别急,渐渐开始了
public Session getSession(SessionKey key) throws SessionException {
Session session = lookupSession(key);
return session != null ? createExposedSession(session, key) : null;
}
// step5. 来了
private Session lookupSession(SessionKey key) throws SessionException {
if (key == null) {
throw new NullPointerException("SessionKey argument cannot be null.");
}
// 看到do开头方法,就知道,开始真正干活了
return doGetSession(key);
}
}
AbstractValidatingSessionManager.java
(DefaultSessionManager
отец)
public abstract class AbstractValidatingSessionManager extends AbstractNativeSessionManager implements ValidatingSessionManager, Destroyable {
// step6. 开始了
@Override
protected final Session doGetSession(final SessionKey key) throws InvalidSessionException {
// 验证session的有效性,一般不启动这个分方法(感觉redis可以设置时间控制有效性,可以不启动验证)
enableSessionValidationIfNecessary();
// 直接看这个吧,这个直接跳到子类DefaultSessionManager中去了,go
Session s = retrieveSession(key);
if (s != null) {
validate(s, key);
}
return s;
}
}
DefaultSessionManager.java
public class DefaultSessionManager extends AbstractValidatingSessionManager implements CacheManagerAware {
// step7. 继续
protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
// 先取sessionId
Serializable sessionId = getSessionId(sessionKey);
if (sessionId == null) {
return null;
}
// 通过sessionId来取session
Session s = retrieveSessionFromDataSource(sessionId);
if (s == null) {
throw new UnknownSessionException(msg);
}
return s;
}
// step8. 开始找sessionId
@Override
public Serializable getSessionId(SessionKey key) {
// 查看key中是否就存储了这个sessionId(shiro到处搞引用缓存,真几把绕)
Serializable id = super.getSessionId(key);
if (id == null && WebUtils.isWeb(key)) {
ServletRequest request = WebUtils.getRequest(key);
ServletResponse response = WebUtils.getResponse(key);
// 继续从这儿开始
id = getSessionId(request, response);
}
return id;
}
// 到这里了,继续
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
return getReferencedSessionId(request, response);
}
// step9. 这里就找完就结束了,没找到就没找到了,开始回到step7,假定已经找到了,ok,继续
private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {
// 这个直接从cookie里面读,这个过程建议自己debug进去看看,我感觉挺重要的,也很简单,我就不写了
String id = getSessionIdCookieValue(request, response);
if (id != null) {
// 保存到request里面去
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
} else {
// 如果没找到就从请求参数里面找,这个请求规则是这样的:http:localhost:8001?;ShiroHttpSession.DEFAULT_SESSION_ID_NAME=sessionId(熟称shiro小尾巴,真心不好看)
//try the URI path segment parameters first:
id = getUriPathSegmentParamValue(request, ShiroHttpSession.DEFAULT_SESSION_ID_NAME);
if (id == null) {
//not a URI path segment parameter, try the query parameters:
String name = getSessionIdName();
id = request.getParameter(name);
if (id == null) {
//try lowercase:
// 还没找到,那就从request请求参数里面去找(所以就算浏览器存不了cookie,那只能保存到请求参数里面了)
id = request.getParameter(name.toLowerCase());
}
}
if (id != null) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
ShiroHttpServletRequest.URL_SESSION_ID_SOURCE);
}
}
// 把刚获取到的结果都放在request里面缓存起来
if (id != null) {
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
}
// always set rewrite flag - SHIRO-361
request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled());
return id;
}
// step10. 从你自己设定的存储来获取session,如果是redis,就从redis里面获取,就到这个了,剩下的自己看吧
protected Session retrieveSessionFromDataSource(Serializable sessionId) throws UnknownSessionException {
return sessionDAO.readSession(sessionId);
}
}
В этот момент,resolveSession(context)
Этот метод является полным, толькоdoCreateSubject(context)
а такжеsave(subject)
public class DefaultSecurityManager extends SessionsSecurityManager {
public Subject createSubject(SubjectContext subjectContext) {
SubjectContext context = copy(subjectContext);
context = ensureSecurityManager(context);
context = resolveSession(context);
context = resolvePrincipals(context);
// 来这个很简单
Subject subject = doCreateSubject(context);
save(subject);
return subject;
}
// 从方法就看出来,最终使用专门的subjectFactory来创建Subject,本文都在讲web
// 所以默认是 DefaultWebSubjectFactory这个工厂方法
protected Subject doCreateSubject(SubjectContext context) {
return getSubjectFactory().createSubject(context);
}
}
DefaultWebSubjectFactory.java
public class DefaultWebSubjectFactory extends DefaultSubjectFactory {
public Subject createSubject(SubjectContext context) {
boolean isNotBasedOnWebSubject = context.getSubject() != null && !(context.getSubject() instanceof WebSubject);
if (!(context instanceof WebSubjectContext) || isNotBasedOnWebSubject) {
return super.createSubject(context);
}
// 从context这个存储里面取值了
WebSubjectContext wsc = (WebSubjectContext) context;
SecurityManager securityManager = wsc.resolveSecurityManager();
Session session = wsc.resolveSession();
boolean sessionEnabled = wsc.isSessionCreationEnabled();
PrincipalCollection principals = wsc.resolvePrincipals(); //
boolean authenticated = wsc.resolveAuthenticated(); // 是否认证了
String host = wsc.resolveHost();
// 还有request和response,是不是subject存了很多,但你却基本上没用过,没事别乱搞事喔
ServletRequest request = wsc.resolveServletRequest();
ServletResponse response = wsc.resolveServletResponse();
// 创建一个真正的Subject
return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled, request, response, securityManager);
}
}
Выше описан процесс создания темы, если есть сессия, то она будет заполнена, если нет сессии, она не будет заполнена, но тему надо создать. Хорошо, теперь вернемся к классу AbstractShiroFilter, продолжим рассмотрение метода doFilterInternal, прелюдии достаточно, и цепочка фильтров широ изменена. Давай давай
AbstractShiroFilter.java (фильтр широ начал распространяться и выполняться)
public abstract class AbstractShiroFilter extends OncePerRequestFilter {
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
throws ServletException, IOException {
Throwable t = null;
try {
final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
// 已经完成
final Subject subject = createSubject(request, response);
//noinspection unchecked
subject.execute(new Callable() {
public Object call() throws Exception {
updateSessionLastAccessTime(request, response);
// 来,come on
executeChain(request, response, chain);
return null;
}
});
} catch (ExecutionException ex) {
t = ex.getCause();
}
if (t != null) {
throw new ServletException(msg, t);
}
}
// 在这里
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
throws IOException, ServletException {
// 这个作用就是根据请求的URL,
// 从 Map<String,String>这个映射中找出第一个匹配的value,以及对应的filtes,映射长这么样
// filterChainDefinitionMap.put("/login", "casFilter");
// filterChainDefinitionMap.put("/favicon.ico", "anon");
// filterChainDefinitionMap.put("/**/*.html", "anon");
// filterChainDefinitionMap.put("/**", "authc,anon"); 这就有2个喔,也就是chain中有两个filter
FilterChain chain = getExecutionChain(request, response, origChain);
// 这个chain就是ProxiedFilterChain(web下就是这个喔),走,去这个类看看,功能很简单
chain.doFilter(request, response);
}
}
ProxiedFilterChain.java
(При наличии нескольких фильтров выполнить стратегию)
public class ProxiedFilterChain implements FilterChain {
// 执行过滤链策略,其实就是把当前chain,当成所有filter的chain,使用本地index变量来确定下一个要执行的filter
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (this.filters == null || this.filters.size() == this.index) {
this.orig.doFilter(request, response);
} else {
this.filters.get(this.index++).doFilter(request, response, this);
}
}
}
До сих пор Широ рассказал больше, чем о половине, и осталось еще запустить процесс фильтрации.В качестве примера я возьму casFilter.
диаграмма последовательности запросов shiro cas
Из рисунка видно, что весь процесс аутентификации, я напрямую поставил casFilter.java, это пользователь, который получил токен, а затем инициировал запрос к серверу, теперь doFilter из CasFilter.java перехватил его, это пора сделать перерыв (используемый ниже абстрактный класс Filter очень знаком, я упоминал его ранее):
OncePerRequestFilter.java (родительский класс CasFilter)
public abstract class OncePerRequestFilter extends NameableFilter {
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain){
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
filterChain.doFilter(request, response);
} else
if (!isEnabled(request, response) || shouldNotFilter(request) ) {
filterChain.doFilter(request, response);
} else {
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
// 直接看这个吧,我直接跳到CasFilter的onAccessDenied方法吧,
// why => 前面这块讲得真的很清楚喔
// 不知道的请跳到前面讲 shiro filter那块
doFilterInternal(request, response, filterChain);
} finally {
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}
}
CasFilter.java
public class CasFilter extends AuthenticatingFilter {
// 前面那些步骤啥也没干,直到这里开始干活了
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
// 这个方法是父类AuthenticatingFilter的,走去这里
return executeLogin(request, response);
}
}
AuthenticatingFilter.java
public abstract class AuthenticatingFilter extends AuthenticationFilter {
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
// 取出请求参数里面的token,并包装成casToken
AuthenticationToken token = createToken(request, response);
try {
// 看到这里清楚了吧,使用SecurityManager提供的接口,开始验证了喔
Subject subject = getSubject(request, response);
// 登录,走起
subject.login(token);
return onLoginSuccess(token, subject, request, response);
} catch (AuthenticationException e) {
return onLoginFailure(token, e, request, response);
}
}
}
DelegatingSubject.java
public class DelegatingSubject implements Subject {
public void login(AuthenticationToken token) throws AuthenticationException {
clearRunAsIdentitiesInternal();
// 直接看这个吧,登录操作肯定交给securityManager了
// step1. 开始的地方
Subject subject = securityManager.login(this, token);
PrincipalCollection principals;
String host = null;
if (subject instanceof DelegatingSubject) {
DelegatingSubject delegating = (DelegatingSubject) subject;
principals = delegating.principals;
host = delegating.host;
} else {
principals = subject.getPrincipals();
}
if (principals == null || principals.isEmpty()) {
throw new IllegalStateException(msg);
}
this.principals = principals;
this.authenticated = true;
if (token instanceof HostAuthenticationToken) {
host = ((HostAuthenticationToken) token).getHost();
}
if (host != null) {
this.host = host;
}
Session session = subject.getSession(false);
if (session != null) {
this.session = decorate(session);
} else {
this.session = null;
}
}
}
DefaultSecurityManager.java
public class DefaultSecurityManager extends SessionsSecurityManager {
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
// step2. 为托给了父类 AuthenticatingSecurityManager
info = authenticate(token);
} catch (AuthenticationException ae) {
try {
// 失败后,估计就跳转登录了
onFailedLogin(token, ae, subject);
} catch (Exception e) {
}
throw ae; //propagate
}
// 认证成功,重新封装subject
Subject loggedIn = createSubject(token, info, subject);
// 这个跟rememberMe有关
onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
}
AuthenticatingSecurityManager.java
public abstract class AuthenticatingSecurityManager extends RealmSecurityManager {
// step3. 继续委托给Authenticator,如果你没配置,默认就是ModularRealmAuthenticator
// 本文还是重新配置了(建议多个realm时必须重新配置这个,
// 除非你的认证策略跟ModularRealmAuthenticator一样)
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
// 直接进入
return this.authenticator.authenticate(token);
}
}
AbstractAuthenticator.java
public abstract class AbstractAuthenticator implements Authenticator, LogoutAware {
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
if (token == null) {
throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
}
AuthenticationInfo info;
try {
// step4. do开头说明真的开始了,
// 本文是自己实现的Authenticator(MyModularRealmAuthenticator),去这里
info = doAuthenticate(token);
if (info == null) {
throw new AuthenticationException(msg);
}
} catch (Throwable t) {
AuthenticationException ae = null;
if (t instanceof AuthenticationException) {
ae = (AuthenticationException) t;
}
if (ae == null) {
ae = new AuthenticationException(msg, t);
}
try {
notifyFailure(token, ae);
} catch (Throwable t2) {
}
throw ae;
}
notifySuccess(token, info);
return info;
}
}
MyModularRealmAuthenticator.java
public class MyModularRealmAuthenticator extends ModularRealmAuthenticator {
// 当有多个realm时,应该如何使用,本文策略就是:如果是castoken就让他走casRealm,其他的走单个认真方式
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
// 所有Realm
Collection<Realm> realms = getRealms();
HashMap<String, Realm> realmHashMap = new HashMap<>(realms.size());
for (Realm realm : realms) {
realmHashMap.put(realm.getName(), realm);
}
if (authenticationToken instanceof CasToken) {
// step5. 直接进入这个方法吧
return doSingleRealmAuthentication(realmHashMap.get("casRealm"), authenticationToken);
} else {
return doSingleRealmAuthentication(realmHashMap.get("tokenRealm"), authenticationToken);
}
}
}
ModularRealmAuthenticator.java
public class ModularRealmAuthenticator extends AbstractAuthenticator {
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
if (!realm.supports(token)) {
throw new UnsupportedTokenException(msg);
}
// step6. 直接进入相应realm了,本文是CasRealm,走去casrealm看看
AuthenticationInfo info = realm.getAuthenticationInfo(token);
if (info == null) {
throw new UnknownAccountException(msg);
}
return info;
}
}
AuthenticatingRealm.java
(CasRealm
отец)
public abstract class AuthenticatingRealm extends CachingRealm implements Initializable {
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 先查看这个realm有没有配置缓存,有的话直接从缓存里面取
// 如果你配置CacheManager,casRealm1.setAuthorizationCachingEnabled(true),则会使用缓存喔,这个在用户名密码登录,在这里加一个缓存,可以加快认证速度,cas则不需要(不是不需要,是不能用)
AuthenticationInfo info = getCachedAuthenticationInfo(token);
if (info == null) {
//otherwise not cached, perform the lookup:
// step7. 来这里吧,一般自己写个realm,就覆盖do开头的方法(因为覆盖就是为了干活喔)
info = doGetAuthenticationInfo(token);
if (token != null && info != null) {
cacheAuthenticationInfoIfPossible(token, info);
}
}
if (info != null) {
assertCredentialsMatch(token, info);
}
return info;
}
}
CasRealm.java
public class CasRealm extends AuthorizingRealm {
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
CasToken casToken = (CasToken) token;
if (token == null) {
return null;
}
String ticket = (String)casToken.getCredentials();
if (!StringUtils.hasText(ticket)) {
return null;
}
// 默认使用 Cas20ServiceTicketValidator 来进行通信,跟前端调后端接口一样
TicketValidator ticketValidator = ensureTicketValidator();
try {
// step8. 来这里吧,要开始跟cas服务器通信了,验证下token的正确性
// 这个过程就不说了,建议自己debug进去看看,是怎么通信的,我在项目里写了这个模拟,可以看看
Assertion casAssertion = ticketValidator.validate(ticket, getCasService());
AttributePrincipal casPrincipal = casAssertion.getPrincipal();
String userId = casPrincipal.getName();
Map<String, Object> attributes = casPrincipal.getAttributes();
// refresh authentication token (user id + remember me)
casToken.setUserId(userId);
String rememberMeAttributeName = getRememberMeAttributeName();
String rememberMeStringValue = (String)attributes.get(rememberMeAttributeName);
boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);
if (isRemembered) {
casToken.setRememberMe(true);
}
List<Object> principals = CollectionUtils.asList(userId, attributes);
PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());
// 上面不多说了设置
return new SimpleAuthenticationInfo(principalCollection, ticket);
} catch (TicketValidationException e) {
throw new CasAuthenticationException("Unable to validate ticket [" + ticket + "]", e);
}
}
// doGetAuthorizationInfo 这个方法必须注意,后去验证角色,角色权限,都会调用到这个方法,
// 因此请务必重写,注意点为:1为角色roles,2角色的权限permission
// PS: 你可以把AuthorizationInfo封装后放在session里,这样每次调用这个方法就从session里面取
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// retrieve user information
SimplePrincipalCollection principalCollection = (SimplePrincipalCollection) principals;
List<Object> listPrincipals = principalCollection.asList();
Map<String, String> attributes = (Map<String, String>) listPrincipals.get(1);
// create simple authorization info
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// add default roles
addRoles(simpleAuthorizationInfo, split(defaultRoles));
// add default permissions
addPermissions(simpleAuthorizationInfo, split(defaultPermissions));
// get roles from attributes
List<String> attributeNames = split(roleAttributeNames);
for (String attributeName : attributeNames) {
String value = attributes.get(attributeName);
addRoles(simpleAuthorizationInfo, split(value));
}
// get permissions from attributes
attributeNames = split(permissionAttributeNames);
for (String attributeName : attributeNames) {
String value = attributes.get(attributeName);
addPermissions(simpleAuthorizationInfo, split(value));
}
return simpleAuthorizationInfo;
}
}
Это почти закончено. Для конкретного использования, пожалуйста, обратитесь к методу онлайн-использования. Вы также можете использовать аннотации в коде. У Shiro есть собственная реализация AOP, и он будет проксировать эти аннотированные классы и методы.
Напоследок поговорим о моментах, которые можно оптимизировать и на которые следует обратить внимание:
-
WebSessionManager
каждый раз, когда вы получаетеsession
всегда изSessionDAO
читать внутри, если кэшredis
, который потребляет производительность, лучше всего переписать метод retrieveSession, сохранить полученную в первый раз Session в запросе и каждый раз извлекать ее отсюда. - это использовать
Redis
будетSession
При сериализации хранилищаSimpleSession
Поля внутри естьtransient
Изменено, будьте внимательны при выборе схемы сериализации. или перепишите самиSimpleSession
, или выберите тот, который не будет проигнорированtransient
Метод сериализации. - Те ресурсы, которые не требуют аутентификации, будут загружены из тех же ресурсов, что и требующие аутентификации.
SessionDAO
получить один разSession
, на самом деле это совершенно ненужно, да, это тоже может быть вWebSessionManager
оптимизация внутри. - Независимо от того, какой запрос отправлен на сервер, сервер сначала сгенерирует сеанс и сохранит его в хранилище сеансов.Если кто-то продолжит запрашивать, хранилище сеансов будет заполнено, что в конечном итоге вызовет атаку типа «отказ в обслуживании». (Решение состоит в том, чтобы поместить неаутентифицированную сессию и аутентифицированную сессию в разные места, или вы можете не сохранять несерьезную сессию, но без сохранения первого входа в систему аутентификация пользователя не приведет к первому посещению пользователя. адрес, и использовать исходный установить адрес, который повлияет на работу пользователя)