前言
Shiro是Apache下的一个开源项目。shiro属于轻量级框架,相对于SpringSecurity简单的多,也没有SpringSecurity那么复杂。以下是我自己学习之后的记录。
Shiro简介
Apache Shiro 是 Java 的一个安全(权限)框架。 Shiro 能够很是容易的开发出足够好的应用,其不只能够用在 JavaSE 环境,也能够用在 JavaEE 环境。Shiro 能够完成:认证、受权、加密、会话管理、与Web 集成、缓存等。相对于SpringSecurity简单的多,也没有SpringSecurity那么复杂。java
Shiro思路
shiro主要有三大功能模块:
- Subject:主体,一般指用户。
- SecurityManager:安全管理器,管理所有Subject,可以配合内部安全组件。(类似于SpringMVC中的DispatcherServlet)
- Realms:用于进行权限信息的验证,一般需要自己实现。
细分功能
- Authentication:身份认证/登录(账号密码验证)。
- Authorization:授权,即角色或者权限验证。
- Session Manager:会话管理,用户登录后的session相关管理。
- Cryptography:加密,密码加密等。
- Web Support:Web支持,集成Web环境。
- Caching:缓存,用户信息、角色、权限等缓存到如redis等缓存中。
- Concurrency:多线程并发验证,在一个线程中开启另一个线程,可以把权限自动传播过去。
- Testing:测试支持;
- Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问。
- Remember Me:记住我,登录后,下次再来的话不用登录了。
- Subject: 主体,可以是任何可以与应用交互的“用户”;
- SecurityManager: 相当于SpringMVC中的DispatcherServlet,是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。
- Authenticator: 认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
- Authrizer: 授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
- Realm: 可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;
- SessionManager: 如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据;这样的话,比如我们在Web环境用,刚开始是一台Web服务器;接着又上了台EJB服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到Memcached服务器);
- SessionDAO: DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;
- CacheManager: 缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
- Cryptography: 密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的。
- 应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;
- 我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入。
引入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.9.0</version>
</dependency>
user.java(用户实体类)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private static final long serialVersionUID = -8170373721622447082L;
private Long id;
private String userName;
private String password;
private String Salt;
/**
* 用户对应的角色集合
*/
private Set<Role> roles;
}
Role.java(角色对应实体类)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Role {
private Long id;
private String roleName;
/**
* 角色对应权限集合
*/
private Set<Permission> permission;
}
Permissions.java(权限对应实体类)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Permission {
private Long id;
private String permissionName;
}
LoginController.java
import com.orm.mybatis.shiro.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class LoginController {
@GetMapping("/login")
public String login(User user) {
if (!StringUtils.hasText(user.getUserName())||!StringUtils.hasText(user.getPassword())) {
return "请输入用户名和密码!";
}
//用户认证信息
Subject subject = SecurityUtils.getSubject();
System.out.println(subject);
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
user.getUserName(),
user.getPassword()
);
try {
// 进行验证,这里可以捕获异常,然后返回对应信息
// 在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查
// 每个Realm都能在必要时对提交的AuthenticationTokens作出反应
// 所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法
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 "没有权限";
}
return "login success";
}
@RequiresRoles("admin")
@GetMapping("/admin")
public String admin() {
return "admin success!";
}
@RequiresPermissions("query")
@GetMapping("/index")
public String index() {
return "index success!";
}
@RequiresPermissions("add")
@GetMapping("/add")
public String add() {
return "add success!";
}
}
LoginServiceImpl.java
import com.orm.mybatis.shiro.entity.Permission;
import com.orm.mybatis.shiro.entity.Role;
import com.orm.mybatis.shiro.entity.User;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Service
public class LoginServiceImpl {
@Resource
private PermissionsServiceImpl permissionsService;
@Resource
private RoleServiceImpl roleService;
@Resource
private UserServiceImpl userService;
/**
*
* @param userName
* @return
*/
public User getUserByName(String userName){
User user = userService.queryByName(userName);
System.out.println("start"+user);
List<Long> roleLongIds = roleService.getRoleIdListByUserId(user.getId());
Set<Role> roleSet = new HashSet<>();
roleLongIds.forEach(
(v)->{
Role role = roleService.queryById(v);
List<Long> permissionLongIds = new ArrayList<>();
Set<Permission> permissionSet = new HashSet<>();
permissionLongIds = permissionsService.getPermissionIdListByRoleId(v);
permissionLongIds.forEach(
(v1)->{
permissionSet.add(permissionsService.queryById(v1));
}
);
role.setPermission(permissionSet);
roleSet.add(role);
}
);
user.setRoles(roleSet);
return user;
}
}
自定义Realm用于查询用户的角色和权限信息并保存到权限管理器:
CustomRealm.java
import com.orm.mybatis.shiro.entity.Permission;
import com.orm.mybatis.shiro.entity.Role;
import com.orm.mybatis.shiro.entity.User;
import com.orm.mybatis.shiro.serviceImpl.LoginServiceImpl;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
public class CustomRealm extends AuthorizingRealm {
@Resource
private LoginServiceImpl 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 (Permission permission : role.getPermission()) {
simpleAuthorizationInfo.addStringPermission(permission.getPermissionName());
}
}
return simpleAuthorizationInfo;
}
/**
* @MethodName doGetAuthenticationInfo
* @Description 认证配置类
* @Param [authenticationToken]
* @Return AuthenticationInfo
* @Author WangShiLin
* 验证当前登录的Subject
* LoginController.login()方法中执行Subject.login()时 执行此方法
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
if (!StringUtils.hasText((String) authenticationToken.getPrincipal())) {
return null;
}
System.out.println("authenticationToken.getCredentials()"+new String((char[])authenticationToken.getCredentials()));
System.out.println((char[])authenticationToken.getCredentials());
//获取用户信息
String name = authenticationToken.getPrincipal().toString();
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;//authenticationToken 是 UsernamePasswordToken 的子类
User user = loginService.getUserByName(name);
if (user == null) {
//这里返回后会报出对应异常
return null;
} else {
//这里验证authenticationToken和simpleAuthenticationInfo的信息
//交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword(),
ByteSource.Util.bytes
(user.getSalt()),getName());
return simpleAuthenticationInfo;
}
}
}
本文所附代码不细看具体细节,这里只总结大致流程,自定义CustomerRealm 继承抽象类 AuthorizingRealm,并实现其两个抽象方法doGetAuthorizationInfo与doGetAuthenticationInfo,两者分别获取数据库中当前Subject(即用户User)的权限信息和认证信息(即密码验证),一般数据库表中权限和角色关系都是多对多,我们可以联表查询到当前用户所有角色中所有权限值。总的来看,自定义CustomerRealm会分别把权限信息与认证密码信息分别封装,以便调用这两个方法时返回数据库中数据。
PasswordHelper
import com.orm.mybatis.shiro.entity.User;
import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
import org.springframework.stereotype.Service;
@Service
public class PasswordHelper {
// 随机字符串作为salt因子
private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
public static final String ALGORITHM_NAME = "md5"; // 基础散列算法
public static final int HASH_ITERATIONS = 2; // 自定义散列次数
public void encryptPassword(User user) {
user.setSalt(randomNumberGenerator.nextBytes().toHex());
String newPassword = new SimpleHash(ALGORITHM_NAME, user.getPassword(),
ByteSource.Util.bytes(user.getSalt()), HASH_ITERATIONS).toHex();
user.setPassword(newPassword);
}
}
ShiroConfig.java
把CustomRealm和SecurityManager等注入到spring容器中
import com.orm.mybatis.shiro.realm.CustomRealm;
import com.orm.mybatis.shiro.utils.PasswordHelper;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
@ConditionalOnMissingBean //springboot shiro开启注释
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
//注入权限管理
@Bean //springboot shiro开启注释
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
// @Bean
// public HashedCredentialsMatcher hashedCredentialsMatcher() {
// HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// hashedCredentialsMatcher.setHashAlgorithmName(PasswordHelper.ALGORITHM_NAME); // 散列算法
// hashedCredentialsMatcher.setHashIterations(PasswordHelper.HASH_ITERATIONS); // 散列次数
// return hashedCredentialsMatcher;
// }
//将自己的验证方式加入容器
//Shiro Realm 继承自AuthorizingRealm的自定义Realm,即指定Shiro验证用户登录的类为自定义的
@Bean
public CustomRealm myShiroRealm() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName(PasswordHelper.ALGORITHM_NAME);
hashedCredentialsMatcher.setHashIterations(PasswordHelper.HASH_ITERATIONS);
CustomRealm customRealm = new CustomRealm();
customRealm.setCredentialsMatcher(hashedCredentialsMatcher);
customRealm.setCacheManager(new EhCacheManager());
//实现用户认证授权之后,发现,每次刷新页面都会进行多次的数据库操作,为了避免这种现象,减轻数据库的负担。
//使用缓存,将查询的数据缓存到cache中,避免多次的查询数据,从而提高系统的查询效率。
//因为开启了debug级别的日志,所以如果控制台没有sql展示说明缓存已开启。
customRealm.setCachingEnabled(true); //开启全局缓存
customRealm.setAuthenticationCachingEnabled(true);
customRealm.setAuthenticationCacheName("authenticationCache");
customRealm.setAuthorizationCachingEnabled(true);
customRealm.setAuthorizationCacheName("authorizationCache");
return customRealm;
}
//权限管理,配置主要是Realm的管理认证
//不指定名字的话,自动创建一个方法名第一个字母小写的bean
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}
//Filter工厂,设置对应的过滤条件和跳转条件
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
log.info("注入Shiro的Web过滤器-->shiroFilter", ShiroFilterFactoryBean.class);
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//Shiro的核心安全接口,这个属性是必须的
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> map = new HashMap<>();
//登出
map.put("/logout", "logout");
//对所有用户认证
map.put("/**", "authc");
//登录
//要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
//首页
//登录成功后要跳转的连接,逻辑也可以自定义,例如返回上次请求的页面
shiroFilterFactoryBean.setSuccessUrl("/index");
//错误页面,认证不通过跳转
//用户访问未对其授权的资源时,所显示的连接
shiroFilterFactoryBean.setUnauthorizedUrl("/error");
/**
* Shiro内置过滤器,能够实现拦截器相关的拦截器
* 经常使用的过滤器:
* anon:无需认证(登陆)能够访问
* authc:必须认证才能够访问
* user:若是使用rememberMe的功能能够直接访问
* perms:该资源必须获得资源权限才能够访问
* role:该资源必须获得角色权限才能够访问
// 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
// <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->**/
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
}
接下来,我们将目光集中到上文的shirFilter方法中。Shiro通过一系列filter来控制访问权限,并在它的内部为我们预先定义了多个过滤器,我们可以直接通过字符串配置这些过滤器。
常用的过滤器如下:
authc
:所有已登陆用户可访问
roles
:有指定角色的用户可访问,通过[ ]指定具体角色,这里的角色名称与数据库中配置一致
perms
:有指定权限的用户可访问,通过[ ]指定具体权限,这里的权限名称与数据库中配置一致
anon
:所有用户可访问,通常作为指定页面的静态资源时使用
@Bean shiroFilterFactoryBean
:获取ShiroFilterFactoryBean,作用是类似于AOP,执行相关操作前,先进行功能过滤,拦截所有请求,进入到shiro中进行认证与授权
@Bean securityManager
:创建SecurityManager,它是shiro的心脏,来管理shiro
@Bean myShiroRealm
:对自定义的CustomerRealm进行一些配置,比如配置凭证校验匹配器了、设置redis缓存了
从三个方法传入参数来看,是呈链式调用的,getShiroFilterFactoryBean()方法需要传入DefaultWebSecurityManager对象,getDefaultWebSecurityManager()方法需要传入Realm(Realm是个接口)实际传入的是myShiroRealm()方法返回的CustomerRealm对象。
注解验证角色和权限的话无法捕捉异常,从而无法正确的返回给前端错误信息,所以我加了一个类用于拦截异常,具体代码如下
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
@Slf4j
public class MyExceptionHandler {
@ExceptionHandler
public String ErrorHandler(AuthorizationException e) {
log.error("没有通过权限验证!", e);
return "my_error";
}
}
切入点在subject.login()实际执行流程(以认证为例)
1.切入点在subject.login()
try {
//在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查
//每个Realm都能在必要时对提交的AuthenticationTokens作出反应
//所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法
//进行验证,这里可以捕获异常,然后返回对应信息
subject.login(usernamePasswordToken);
// subject.checkRole("admin");
// subject.checkPermissions("query", "add");
System.out.println("usernamePasswordToken"+usernamePasswordToken);
} catch (UnknownAccountException e) {
log.error("用户名不存在!", e);
return "用户名不存在!";
} catch (AuthenticationException e) {
log.error("账号或密码错误!", e);
return "账号或密码错误!";
} catch (AuthorizationException e) {
log.error("没有权限!", e);
return "没有权限";
}
2.Subject是个接口,这里的login方法由DelegatingSubject实现
public class DelegatingSubject implements Subject {
可以看到,DelegatingSubject中的login()方法又调用了SecurityManager的login()方法
注意调试的时候,要下载shiro框架的源码来查看对应的java文件,不要去看class文件里的东西,java文件里更清晰
public void login(AuthenticationToken token) throws AuthenticationException {
clearRunAsIdentitiesInternal();
Subject subject = securityManager.login(this, token);
3.进入securityManager.login()方法,发现执行的是DefaultSecurityManager的login()方法,并调用了authenticate()方法,其中DefaultSecurityManager继承的父类实现了SecurityManager接口
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = authenticate(token);
4.这个authenticate()方法属于抽象类AuthenticatingSecurityManager,转而调用了this.authenticator的authenticate()方法
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
return this.authenticator.authenticate(token);
}
5.这个this.authenticator是该抽象类中定义的private Authenticator authenticator; Authenticator 是个接口,实际执行的是其实现类AbstractAuthenticator的authenticate()方法
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
if (token == null) {
throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
}
log.trace("Authentication attempt received for token [{}]", token);
AuthenticationInfo info;
try {
info = doAuthenticate(token);
6.AbstractAuthenticator是个抽象类,并执行了其doAuthenticate()方法,该方法是个抽象方法,实际执行的是其实现类ModularRealmAuthenticator中的方法。
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)
throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(realms, authenticationToken);
}
}
7.进入当前类下的doSingleRealmAuthentication()方法,然后去执行realm.getAuthenticationInfo()方法。
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token)
{
if (!realm.supports(token)) {
String msg = "Realm [" + realm + "] does not support authentication token [" +
token + "]. Please ensure that the appropriate Realm implementation is " +
"configured correctly or that the realm accepts AuthenticationTokens of this type.";
throw new UnsupportedTokenException(msg);
}
AuthenticationInfo info = realm.getAuthenticationInfo(token);
8.Realm是个接口,实际调用的是抽象类AuthenticatingRealm的方法。
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
AuthenticationInfo info = getCachedAuthenticationInfo(token);
if (info == null) {
//otherwise not cached, perform the lookup:
info = doGetAuthenticationInfo(token);
......//省略一部分
if (info != null) {
assertCredentialsMatch(token, info);
}
9.先从缓存中取,若取不到,则执行doGetAuthenticationInfo()方法,绕了这么久终于来了!!!还记得自定义的CustomerRealm 继承 抽象类AuthorizingRealm,并实现了其两个抽象方法,其中一个就是认证的doGetAuthenticationInfo(),这里从数据库读取用户名密码。
10.然后进入assertCredentialsMatch方法中进行密码校验,token中是new UsernamePasswordToken(username,password)前端传进来的用户名密码,info中是数据库查到的用户名密码及随机盐。
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
CredentialsMatcher cm = getCredentialsMatcher();
if (cm != null) {
if (!cm.doCredentialsMatch(token, info)) {
//not successful - throw an exception to indicate this:
String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
throw new IncorrectCredentialsException(msg);
}
11.cm.doCredentialsMatch(token, info)方法是由我们realm中指定的密码校验器HashedCredentialsMatcher实现的,在这里完成校验。到这里就不继续看了。
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenHashedCredentials = hashProvidedCredentials(token, info);
Object accountCredentials = getCredentials(info);
return equals(tokenHashedCredentials, accountCredentials);
}
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)
throws AuthenticationException {
CredentialsMatcher cm = getCredentialsMatcher();
if (cm != null) {
if (!cm.doCredentialsMatch(token, info)) {
//not successful - throw an exception to indicate this:
String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
throw new IncorrectCredentialsException(msg);
}
测试
项目地址
https://gitee.com/liuweiqiang12/springboot