Общая документация:Каталог статей
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: Используйте алгоритмы шифрования, чтобы обеспечить безопасность данных, оставаясь при этом простым в использовании.
и некоторые дополнительные особенности:
- 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 分别处理
Логика запроса:
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: Система аномалий Широ
Совершенно очевидно, что в ответ на сбой аутентификации Широ возникнет исключение, и направление бизнеса может быть определено обработкой исключения.
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, аутентифицировать и привлекать пользователей с помощью соответствующих методов.
В то же время он обеспечивает детализированную поддержку и слабо связан с другими проектами.Мы использовали его детализированные возможности, когда существовала структура аутентификации, потому что этоавторизовавшись вручную, в основном нет конфликтов, и он также очень прост в использовании.