Инфраструктура аутентификации инвентаря: краткий обзор Широ

Java
Инфраструктура аутентификации инвентаря: краткий обзор Широ

Общая документация:Каталог статей
Github : github.com/black-ant

Введение

Я уже говорил о SpringSecurity и Pac4j и планирую улучшить Shiro и CAS в будущем.Вся структура Shiro относительно проста.Эта статья просто краткая, не слишком глубокая.

1.1 Базовые знания

Базовым знаниям Широ рекомендуется ознакомиться с официальной документацией.Shiro Doc, вот простой список

Широ имеет очень простую архитектуру (Subject, SecurityManager и Realms), которая примерно такая же, как и процесс

ApplicationCode -->  Subject (Current User)
				       |
                     SecurityManager (Managers all Subject)
				       |
				   Realms

Краеугольный камень Широ

Широ внутри определяет 4 функциональных краеугольных камня, которые делятся на аутентификацию, авторизацию, управление сеансами и криптографию.

  • Authentication: аутентификация личности, акт подтверждения личности пользователя
  • Authorization: процесс контроля доступа, т. е. определение того, кто и к чему имеет доступ.
  • Session Management: Управление сеансами для конкретных пользователей даже в приложениях, не являющихся веб-приложениями или EJB-приложениями.
  • Cryptography: Используйте алгоритмы шифрования, чтобы обеспечить безопасность данных, оставаясь при этом простым в использовании.

image.png

image.png

и некоторые дополнительные особенности:

  • Web Support: API веб-поддержки Shiro помогает легко защитить веб-приложения.
  • Caching: Кэширование — это первый уровень в Apache Shiro API, который используется для обеспечения того, чтобы операции безопасности оставались быстрыми и эффективными.
  • Concurrency: Apache Shiro поддерживает многопоточные приложения и его функции параллелизма.
  • Run As: функция, которая позволяет пользователю принять личность другого пользователя (я понимаю, что это прокси)
  • Remember Me: функция "запомнить меня"

Дополнительные скрытые понятия:

  • Permission: разрешение
  • Role: роль

2. Основное использование

Использование Широ для меняПервое впечатление чисто, вам не нужно обращать внимание на множество настроек, таких как SpringSecurity, обращайте внимание на множество фильтров, и вам не нужно много работать с WebFlow, например с исходным кодом CAS,Все сертификаты делаем сами.

2.1 Класс конфигурации

@Configuration
public class shiroConfig {


    /**
     * 配置 Realm
     *
     * @return
     */
    @Bean
    public CustomRealm myShiroRealm() {
        CustomRealm customRealm = new CustomRealm();
        return customRealm;
    }

    /**
     * 权限管理,配置主要是Realm的管理认证
     * @return
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }

    //Filter工厂,设置对应的过滤条件和跳转条件
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, String> map = new HashMap<>();
        // logout url
        map.put("/logout", "logout");
        //对所有用户认证
        map.put("/**", "authc");
        //登录
        shiroFilterFactoryBean.setLoginUrl("/login");
        //首页
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //错误页面,认证不通过跳转
        shiroFilterFactoryBean.setUnauthorizedUrl("/error");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    /**
     * 注册 SecurityManager
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }


    /**
     * AOP 注解冲突解决方式
     *
     * @return
     */
    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
        defaultAAP.setProxyTargetClass(true);
        return defaultAAP;
    }
} 

2.2 Инициировать аутентификацию

Инициация аутентификации Широ очень проста и полностью инициируется вручную.

     Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
                user.getUserName(),
                user.getPassword()
        );
        try {
            //进行验证,这里可以捕获异常,然后返回对应信息
            subject.login(usernamePasswordToken);
//            subject.checkRole("admin");
//            subject.checkPermissions("query", "add");
        } catch (UnknownAccountException e) {
            log.error("用户名不存在!", e);
            return "用户名不存在!";
        } catch (AuthenticationException e) {
            log.error("账号或密码错误!", e);
            return "账号或密码错误!";
        } catch (AuthorizationException e) {
            log.error("没有权限!", e);
            return "没有权限";
        }

Поскольку он инициируется полностью вручную, при интеграции Широ нет давления, вы можете самостоятельно инкапсулировать любой интерфейс во внешнем слое, и вы можете делать что угодно в интерфейсе.

2.3 Логика проверки

public class CustomRealm extends AuthorizingRealm {

    @Autowired
    private LoginService loginService;

    /**
     * @MethodName doGetAuthorizationInfo
     * @Description 权限配置类
     * @Param [principalCollection]
     * @Return AuthorizationInfo
     * @Author WangShiLin
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取登录用户名
        String name = (String) principalCollection.getPrimaryPrincipal();
        //查询用户名称
        User user = loginService.getUserByName(name);
        //添加角色和权限
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        for (Role role : user.getRoles()) {
            //添加角色
            simpleAuthorizationInfo.addRole(role.getRoleName());
            //添加权限
            for (Permissions permissions : role.getPermissions()) {
                simpleAuthorizationInfo.addStringPermission(permissions.getPermissionsName());
            }
        }
        return simpleAuthorizationInfo;
    }

    /**
     * @MethodName doGetAuthenticationInfo
     * @Description 认证配置类
     * @Param [authenticationToken]
     * @Return AuthenticationInfo
     * @Author WangShiLin
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        if (StringUtils.isEmpty(authenticationToken.getPrincipal())) {
            return null;
        }
        //获取用户信息
        String name = authenticationToken.getPrincipal().toString();
        User user = loginService.getUserByName(name);
        if (user == null) {
            //这里返回后会报出对应异常
            return null;
        } else {
            //这里验证authenticationToken和simpleAuthenticationInfo的信息
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword().toString(), getName());
            return simpleAuthenticationInfo;
        }
    }
}

LoginServiceImpl тоже просто выложен, просто для получения пользователей из источника данных

@Service
public class LoginServiceImpl implements LoginService {

    @Autowired
    private PermissionServiceImpl permissionService;

    @Override
    public User getUserByName(String getMapByName) {
        return getMapByName(getMapByName);
    }

    /**
     * 模拟数据库查询
     *
     * @param userName 用户名
     * @return User
     */
    private User getMapByName(String userName) {

        // 构建 Role 1
        Role role = new Role("1", "admin", getAllPermission());
        Set<Role> roleSet = new HashSet<>();
        roleSet.add(role);

        // 构建 Role 2
        Role role1 = new Role("2", "user", getSinglePermission());
        Set<Role> roleSet1 = new HashSet<>();
        roleSet1.add(role1);

        User user = new User("1", "root", "123456", roleSet);
        Map<String, User> map = new HashMap<>();
        map.put(user.getUserName(), user);

        User user1 = new User("2", "zhangsan", "123456", roleSet1);
        map.put(user1.getUserName(), user1);

        return map.get(userName);
    }

    /**
     * 权限类型一
     */
    private Set<Permissions> getAllPermission() {
        Set<Permissions> permissionsSet = new HashSet<>();
        permissionsSet.add(permissionService.getPermsByUserId("1"));
        permissionsSet.add(permissionService.getPermsByUserId("2"));
        return permissionsSet;
    }

    /**
     * 权限类型二
     */
    private Set<Permissions> getSinglePermission() {
        Set<Permissions> permissionsSet1 = new HashSet<>();
        permissionsSet1.add(permissionService.getPermsByUserId("1"));
        return permissionsSet1;
    }

}

LoginServiceImplНа самом деле, его нельзя рассматривать как члена всей системы сертификации Широ, это просто бизнес, управляемый пользователями, так что же остается делать?

  • Написать интерфейс API
  • Подготовьте мир
  • Инициировать аутентификацию через тему
  • Аннотировать соответствующие аннотации на интерфейсе

Весь процесс прост, удобен и легко интегрируется с функцией аутентификации.

3. Исходный код

Как обычно, давайте посмотрим на исходный код, проанализировав его по четырем измерениям:

  • запрос на перехват
  • Оформление запроса
  • процесс сертификации
  • процесс выхода

3.1 Перехват запросов

Это начинается с класса ShiroFilterFactoryBean.

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //....
        //登录
        shiroFilterFactoryBean.setLoginUrl("/login");
        //首页
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //错误页面,认证不通过跳转
        shiroFilterFactoryBean.setUnauthorizedUrl("/error");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }
    
    
C- ShiroFilterFactoryBean
    ?- 构建 ShiroFilterFactoryBean 时会为其配置一个 login 地址
    M- applyGlobalPropertiesIfNecessary : 配置全局属性
        - applyLoginUrlIfNecessary(filter);
        - applySuccessUrlIfNecessary(filter);
        - applyUnauthorizedUrlIfNecessary(filter);
        
    M- applyLoginUrlIfNecessary
        ?- 为 Filter 配置 loginUrl
        - String existingLoginUrl = acFilter.getLoginUrl();
        - acFilter.setLoginUrl(loginUrl)





// 这里对所有的 地址做了拦截
C01- PathMatchingFilter
    F- protected Map<String, Object> appliedPaths = new LinkedHashMap<String, Object>();
        ?- 所有的path均会在这里处理
        - 拦截成功了会调用 isFilterChainContinued , 最终会调用 onAccessDenied -> M2_01
        
C02- FormAuthenticationFilter
    M2_01- onAccessDenied  
        - 
    
// 判断是否需要重定向
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        if (isLoginRequest(request, response)) {
            if (isLoginSubmission(request, response)) {
                return executeLogin(request, response);
            } else {
                return true;
            }
        } else {
            // 重定向到 login 页
            saveRequestAndRedirectToLogin(request, response);
            return false;
        }
    }
    
// 当然还有已登录得逻辑 , 已登录是在上面之前判断得
C- AccessControlFilter
    M- onPreHandle
        ?- 该方法中会调用其他得 Filter 判断是否登录

// 例如这里就是 AuthenticationFilter 获取 Subject
C- AuthenticationFilter
    M- isAccessAllowed
         - Subject subject = getSubject(request, response);
         - return subject.isAuthenticated() && subject.getPrincipal() != null;
         

Общая цепочка вызовов, вероятно,

  • OncePerRequestFilter # doFilter
  • AbstractShiroFilter # call
  • AbstractShiroFilter # executeChain
  • ProxiedFilterChain # doFilter
  • AdviceFilter # doFilterInternal
  • PathMatchingFilter # preHandle

В конечном итоге он будет перенаправлен FormAuthenticationFilter из-за цепочки фильтров.

3.2 Способ перехвата


按照我们的常规思路 , 拦截仍然是通过 Filter 来完成
    
    
C- AbstractShiroFilter
    M- doFilterInternal
        - final Subject subject = createSubject(request, response) : 通过 请求构建了一个 Subject
        - 调用 Subject 的 Callable 回调
            - updateSessionLastAccessTime(request, response);
            - executeChain(request, response, chain);
    M- executeChain
        - 执行 FilterChain 判断
        - 
        
// 这里往上追溯 , 可以看到实际上是一个 AOP 操作 : ReflectiveMethodInvocation
// 再往上就是 AopAllianceAnnotationsAuthorizingMethodInterceptor , 注意这里面是懒加载的
        
C03- AnnotationsAuthorizingMethodInterceptor : 通过 Interceptor 对方法进行拦截
	M3_01- assertAuthorized : 断言认证信息
        - 获取一个集合 Collection<AuthorizingAnnotationMethodInterceptor>
       	FOR- 循环 AuthorizingAnnotationMethodInterceptor -> PS301
        	- assertAuthorized  -> M3_05

C- AuthorizingAnnotationMethodInterceptor
	M3_05- assertAuthorized(MethodInvocation mi) 
        - ((AuthorizingAnnotationHandler)getHandler()).assertAuthorized(getAnnotation(mi))
        	- 这里是获取 Method 上面的 Annotation , 再调用 assertAuthorized 验证 -> M5_01
        
 // 补充 : PS301 
TODO    

C05- RoleAnnotationHandler
    M5_01- assertAuthorized(Annotation a)
		- 如果不是 RequiresRoles , 则直接返回
		- getSubject().checkRole(roles[0]) -> M6_02
		- getSubject().checkRoles(Arrays.asList(roles));
			?- 注意 , 这里是区别 And 和 Or 将 roles 分别处理

Логика запроса:

M3_01
M3_05
M5_01
M6_02
M10_05
M11_04
M11_05

AuthorizingAnnotationMethodInterceptor.png

Shiro001.jpg

3.3 Полный процесс сертификации

Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(user.getUserName(),user.getPassword());

subject.login(usernamePasswordToken);

// 来看一下 , 整个流程中做了什么
C06- DelegatingSubject
    M6_01- login(AuthenticationToken token)
    	- Subject subject = securityManager.login(this, token) : 调用 securityManager 完成认证 :M8_01
    M6_02- checkRole(String role)
    	-  securityManager.checkRole(getPrincipals(), role) -> M10_04
    

C07- DefaultWebSecurityManager
 	M7_01-    
    
C08- DefaultSecurityManager
	M8_01- login(Subject subject, AuthenticationToken token) 
    	- 底层调用 AuthorizingRealm 完成认证 , 此处的认证类为自定义的 CustomRealm
   
C09- AbstractAuthenticator 
    M9_01- authenticate(AuthenticationToken token)
    M9_02-
    
C10- ModularRealmAuthenticator
    M10_01- doAuthenticate(AuthenticationToken authenticationToken)
    	- 根据 Realm 数量 , 选择不同的认证类
    		- doSingleRealmAuthentication(realms.iterator().next(), authenticationToken) -> M10_02
    		- doMultiRealmAuthentication(realms, authenticationToken) -> M10_03
    M10_02- doSingleRealmAuthentication
    	- AuthenticationInfo info = realm.getAuthenticationInfo(token) 
    M10_03- doMultiRealmAuthentication
    M10_04- checkRole
    	- hasRole(principals, role) 判断是否 -> M10_05
    M10_05- hasRole
    	FOR- getRealms() : 获取当前的 realms 类
    		- ((Authorizer) realm).hasRole(principals, roleIdentifier) : 调用 Reamlm 判断是否有 Role  -> M11_04
    
C11- AuthenticatingRealm
    M11_01- getAuthenticationInfo(AuthenticationToken token)
    	- getCachedAuthenticationInfo(token) : 获取缓存的 Authentication
    	- 调用 doGetAuthenticationInfo 进行实际的认证 : M12_02
    	- cacheAuthenticationInfoIfPossible 缓存认证信息
	M11_02- cacheAuthenticationInfoIfPossible
    	- getAvailableAuthenticationCache() : 获取缓存集合
    	- getAuthenticationCacheKey(token) : 获取缓存 key
    	- cache.put(key, info) : 添加缓存
	M11_03- assertCredentialsMatch
    	- CredentialsMatcher cm = getCredentialsMatcher();
    	- cm.doCredentialsMatch(token, info)
	M11_04- hasRole
		- AuthorizationInfo info = getAuthorizationInfo(principal) 
            -> M11_05
		- hasRole(roleIdentifier, info)
	M11_05- getAuthorizationInfo(PrincipalCollection principals)
		?- 注意这里和 M11_01 的参数是不一样的
		- getAvailableAuthorizationCache() 获取 Cache<Object, AuthorizationInfo>
		- 如果 Cache 不为空 , getAuthorizationCacheKey 获取 key 后通过该key 从 Cache 里面获取 AuthorizationInfo
		- 如果 缓存获取失败 , 则调用 doGetAuthorizationInfo(PrincipalCollection principals) 获取对象
            ?- 注意 , 这里要为其添加 Role -> M12_01
            
            
    
C12- CustomRealm
    M12_01- AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection)
    	
    M12_02- AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)

3.4 Процесс выхода из системы

Что делается при выходе из системы?

logout в конечном итоге вызовет операцию выхода субъекта

    public void logout() {
        try {
            // 本质上是从 Session 中移除 .RUN_AS_PRINCIPALS_SESSION_KEY
            clearRunAsIdentitiesInternal();
            this.securityManager.logout(this);
        } finally {
            // 把 Subject
            this.session = null;
            this.principals = null;
            this.authenticated = false;
        }
    }
    
C- DefaultSecurityManager
    M- logout
        - beforeLogout(subject)
        - subject.getPrincipals() 获取一个 PrincipalCollection 
        - 再获取一个 Authenticator , 通过它来 onLogout PrincipalCollection
            ?- ((LogoutAware) authc).onLogout(principals)
            ?- 需要这一步是因为可能存在 缓存和多模块登录 , 需要同时退出
        // 最后移除 session , 删除 subject 
        - delete(subject);
            -  this.subjectDAO.delete(subject)
        - stopSession(subject);

Логаут Широ тоже выглядит ясно,Сессия закрывается, тема удаляется, и конец .

Вам даже не нужно думать, нужна ли вам переадресация, все решает бизнес.

3.5 Дополнение 1: Система аномалий Широ

Совершенно очевидно, что в ответ на сбой аутентификации Широ возникнет исключение, и направление бизнеса может быть определено обработкой исключения.

Shiro-AccountException.png

3.6 Дополнение 2: Детали DefaultSubjectDAO

DefaultSubjectDAO — одна из наиболее важных логик, которая отвечает за обработку связанного сохранения субъекта,Конечно, среди пользователей мы можем сделать свой собственный класс реализации для обработки работы Субъекта.


private SessionStorageEvaluator sessionStorageEvaluator;

// 主要看一下其中的 CURD 操作
M- saveToSession(Subject subject)
    - mergePrincipals(subject);
    - mergeAuthenticationState(subject);
    
M- mergePrincipals
    ?- 将主体当前的Subject#getPrincipals()与可能在其中的任何元素合并到任何可用的会话
    - currentPrincipals = subject.getPrincipals();
    - Session session = subject.getSession(false);
    - session.removeAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
    - session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, currentPrincipals)
    
// 不用多说什么 , 很清晰的就能看到 , 将 currentPrincipals 设置到了可用的 Session 中 , 也就是说 , Principals 其实是在 Session 中流转

M- mergeAuthenticationState
    - session = subject.getSession();
    - session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, Boolean.TRUE);
    - session.removeAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY);
    
// 一样的 , 通过 Session 控制

3.7 Логика управления Субъектом

Когда я увидел, что Subject был получен раньше, он был получен через getSubject, взгляните на этот класс


// 看了这个类大概就知道 , 为什么 shiro 支持多线程
public static Subject getSubject() {
    Subject subject = ThreadContext.getSubject();
    if (subject == null) {
        subject = (new Subject.Builder()).buildSubject();
        ThreadContext.bind(subject);
    }
    return subject;
}

// PS : ThreadContext 是 Shiro 自己的工具类


// TODO : 这里先留一个小坑 , 多线程的处理逻辑还没有专门分析 , 后续进行补充




4. Расширение — определите процесс OAuth самостоятельно.

Из-за особенностей Shiro режим OAuth фактически интегрирован с другими пакетами.

Ссылка на @woohoo.ой-узнай.талант/тема/15938… , Этот раздел неполный, рекомендуется обращаться к исходному тексту

Maven: Это не обязательно, другие реализации OAuth могут быть

<dependency>
    <groupId>org.apache.oltu.oauth2</groupId>
    <artifactId>org.apache.oltu.oauth2.authzserver</artifactId>
    <version>1.0.2</version>
</dependency>
<dependency>
    <groupId>org.apache.oltu.oauth2</groupId>
    <artifactId>org.apache.oltu.oauth2.resourceserver</artifactId>
    <version>1.0.2</version>
</dependency>

Другой в целом более 2-х интерфейсов

    @RequestMapping("/authorize")
    public Object authorize(Model model, HttpServletRequest request)
            throws URISyntaxException, OAuthSystemException {
        logger.info("------ > 第一步 进入验证申请", request.toString());
        try {
            logger.info("------ > 第二步 生成 OAuthAuthzRequest", request.toString());
            OAuthAuthzRequest  oauthRequest = new OAuthAuthzRequest(request);
            //检查传入的客户端id是否正确
            if (!oAuthService.checkClientId(oauthRequest.getClientId())) {
                OAuthResponse response = OAuthASResponse
                        .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
                        .setError(OAuthError.TokenResponse.INVALID_CLIENT)
                        .setErrorDescription(Constants.INVALID_CLIENT_DESCRIPTION)
                        .buildJSONMessage();
                return new ResponseEntity(
                        response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
            }
            logger.info("step 3 获取 subject---:{}", SecurityUtils.getSubject().toString());
            Subject subject = SecurityUtils.getSubject();
            //如果用户没有登录,跳转到登陆页面
            if (!subject.isAuthenticated()) {
                if (!login(subject, request)) {//登录失败时跳转到登陆页面
                    model.addAttribute("client",
                            clientService.findByClientId(oauthRequest.getClientId()));
                    return "oauth2login";
                }
            }
            logger.info("step 4 获取 username---:{}", (String) subject.getPrincipal());
            String username = (String) subject.getPrincipal();
            //生成授权码
            String authorizationCode = null;
            //responseType目前仅支持CODE,另外还有TOKEN
            String responseType = oauthRequest.getParam(OAuth.OAUTH_RESPONSE_TYPE);
            if (responseType.equals(ResponseType.CODE.toString())) {
                OAuthIssuerImpl oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
                authorizationCode = oauthIssuerImpl.authorizationCode();
                logger.info("step 5 step  -- authorizationCode :{}", authorizationCode);
                oAuthService.addAuthCode(authorizationCode, username);
            }
            //进行OAuth响应构建
            OAuthASResponse.OAuthAuthorizationResponseBuilder builder =
                    OAuthASResponse.authorizationResponse(request,
                            HttpServletResponse.SC_FOUND);
            logger.info("step 5 step  -- OAuthAuthorizationResponseBuilder :{}", builder);
            //设置授权码
            builder.setCode(authorizationCode);
            //得到到客户端重定向地址
            String redirectURI = oauthRequest.getParam(OAuth.OAUTH_REDIRECT_URI);

            //构建响应
            final OAuthResponse response = builder.location(redirectURI).buildQueryMessage();
            //根据OAuthResponse返回ResponseEntity响应
            HttpHeaders headers = new HttpHeaders();
            headers.setLocation(new URI(response.getLocationUri()));
            return new ResponseEntity(headers, HttpStatus.valueOf(response.getResponseStatus()));
        } catch (OAuthProblemException e) {
            //出错处理
            logger.info("step 2 进入authorize OAuthAuthzRequest---:{}", request.toString());
            String redirectUri = e.getRedirectUri();
            if (OAuthUtils.isEmpty(redirectUri)) {
                //告诉客户端没有传入redirectUri直接报错
                return new ResponseEntity(
                        "OAuth callback url needs to be provided by client!!!", HttpStatus.NOT_FOUND);
            }
            //返回错误消息(如?error=)
            final OAuthResponse response =
                    OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND)
                            .error(e).location(redirectUri).buildQueryMessage();
            HttpHeaders headers = new HttpHeaders();
            headers.setLocation(new URI(response.getLocationUri()));
            return new ResponseEntity(headers, HttpStatus.valueOf(response.getResponseStatus()));
        }
    }

    private boolean login(Subject subject, HttpServletRequest request) {
        if ("get".equalsIgnoreCase(request.getMethod())) {
            return false;
        }
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            return false;
        }

        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            subject.login(token);
            return true;
        } catch (Exception e) {
            request.setAttribute("error", "登录失败:" + e.getClass().getName());
            return false;
        }
    }

AccessToken

@RequestMapping("/accessToken")
    public HttpEntity token(HttpServletRequest request)
            throws URISyntaxException, OAuthSystemException {
        try {
            //构建OAuth请求
            OAuthTokenRequest oauthRequest = new OAuthTokenRequest(request);
            logger.info("step 1 OAuthTokenRequest request---:{}", oauthRequest.toString());
            //检查提交的客户端id是否正确
            if (!oAuthService.checkClientId(oauthRequest.getClientId())) {
                OAuthResponse response = OAuthASResponse
                        .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
                        .setError(OAuthError.TokenResponse.INVALID_CLIENT)
                        .setErrorDescription(Constants.INVALID_CLIENT_DESCRIPTION)
                        .buildJSONMessage();
                return new ResponseEntity(
                        response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
            }

            // 检查客户端安全KEY是否正确
            if (!oAuthService.checkClientSecret(oauthRequest.getClientSecret())) {
                OAuthResponse response = OAuthASResponse
                        .errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
                        .setError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)
                        .setErrorDescription(Constants.INVALID_CLIENT_DESCRIPTION)
                        .buildJSONMessage();
                return new ResponseEntity(
                        response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
            }
            logger.info("step 1 authCode request---:{}",oauthRequest.getParam(OAuth.OAUTH_CODE));
            String authCode = oauthRequest.getParam(OAuth.OAUTH_CODE);
            // 检查验证类型,此处只检查AUTHORIZATION_CODE类型,其他的还有PASSWORD或REFRESH_TOKEN
            if (oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals(
                    GrantType.AUTHORIZATION_CODE.toString())) {
                if (!oAuthService.checkAuthCode(authCode)) {
                    OAuthResponse response = OAuthASResponse
                            .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
                            .setError(OAuthError.TokenResponse.INVALID_GRANT)
                            .setErrorDescription("错误的授权码")
                            .buildJSONMessage();
                    return new ResponseEntity(
                            response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
                }
            }

            //生成Access Token
            OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
            final String accessToken = oauthIssuerImpl.accessToken();
            logger.info("step 1 accessToken request---:{}",accessToken);
            logger.info("step 1 username data---:{}",  oAuthService.getUsernameByAuthCode(authCode));
            oAuthService.addAccessToken(accessToken,
                    oAuthService.getUsernameByAuthCode(authCode));

            //生成OAuth响应
            OAuthResponse response = OAuthASResponse
                    .tokenResponse(HttpServletResponse.SC_OK)
                    .setAccessToken(accessToken)
                    .setExpiresIn(String.valueOf(oAuthService.getExpireIn()))
                    .buildJSONMessage();

            //根据OAuthResponse生成ResponseEntity
            return new ResponseEntity(
                    response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
        } catch (OAuthProblemException e) {
            //构建错误响应
            OAuthResponse res = OAuthASResponse
                    .errorResponse(HttpServletResponse.SC_BAD_REQUEST).error(e)
                    .buildJSONMessage();
            return new ResponseEntity(res.getBody(), HttpStatus.valueOf(res.getResponseStatus()));
        }
    }

В заключение

Проще говоря, Shiro не поддерживает OAuth, и если вы хотите получить возможности OAuth, вы можете настроить его самостоятельно, просто рассматривая Shiro как внутренний SSO и получая информацию о пользователе.

основной код

Subject subject = SecurityUtils.getSubject();
String username = (String) subject.getPrincipal();

Суммировать

Статья Широ тоже закончилась.Она очень поверхностна и ничего глубокого не говорит.Одна из главных причин в том, что позиционирование Широ должно быть простым.

Он предоставляет вам только возможность аутентификации, и вам нужно только относиться к нему как к внутреннему SSO, аутентифицировать и привлекать пользователей с помощью соответствующих методов.

В то же время он обеспечивает детализированную поддержку и слабо связан с другими проектами.Мы использовали его детализированные возможности, когда существовала структура аутентификации, потому что этоавторизовавшись вручную, в основном нет конфликтов, и он также очень прост в использовании.