springboot2.0+mybatis整合shiro

前言

Shiro是Apache下的一个开源项目。shiro属于轻量级框架,相对于SpringSecurity简单的多,也没有SpringSecurity那么复杂。以下是我自己学习之后的记录。

Shiro简介

Apache Shiro 是 Java 的一个安全(权限)框架。 Shiro 能够很是容易的开发出足够好的应用,其不只能够用在 JavaSE 环境,也能够用在 JavaEE 环境。Shiro 能够完成:认证、受权、加密、会话管理、与Web 集成、缓存等。相对于SpringSecurity简单的多,也没有SpringSecurity那么复杂。java

Shiro思路

在这里插入图片描述

shiro主要有三大功能模块:

  1. Subject:主体,一般指用户。
  2. SecurityManager:安全管理器,管理所有Subject,可以配合内部安全组件。(类似于SpringMVC中的DispatcherServlet)
  3. Realms:用于进行权限信息的验证,一般需要自己实现。

细分功能

  1. Authentication:身份认证/登录(账号密码验证)。
  2. Authorization:授权,即角色或者权限验证。
  3. Session Manager:会话管理,用户登录后的session相关管理。
  4. Cryptography:加密,密码加密等。
  5. Web Support:Web支持,集成Web环境。
  6. Caching:缓存,用户信息、角色、权限等缓存到如redis等缓存中。
  7. Concurrency:多线程并发验证,在一个线程中开启另一个线程,可以把权限自动传播过去。
  8. Testing:测试支持;
  9. Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问。
  10. Remember Me:记住我,登录后,下次再来的话不用登录了。

在这里插入图片描述

  1. Subject: 主体,可以是任何可以与应用交互的“用户”;
  2. SecurityManager: 相当于SpringMVC中的DispatcherServlet,是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。
  3. Authenticator: 认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
  4. Authrizer: 授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
  5. Realm: 可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;
  6. SessionManager: 如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据;这样的话,比如我们在Web环境用,刚开始是一台Web服务器;接着又上了台EJB服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到Memcached服务器);
  7. SessionDAO: DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;
  8. CacheManager: 缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
  9. Cryptography: 密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的。

在这里插入图片描述

  1. 应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;
  2. 我们需要给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


如果这篇文章对您有帮助,可否支持一下博主?
点赞+关注呗,谢谢您。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叶孤崖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值