springboot下shiro和jwt的整合使用

首先shiro做安全框架,将JWT整合到shiro的认证和授权中进行使用,最终的目的就是用户在进行登录的时候,验证成功则返回一个token,后面该用户可以通过这个token是访问自己有权限的接口,启动缓存功能,只有第一次认证需要查询DB,后续访问都是访问缓存(分两种,单机版应用使用EhCache,分布式使用redis)

整合思路:
1.构建一个springBoot项目,引入核心依赖并展示核心代码目录

		<!--引入ehcache依赖(单机版本缓存用) -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.5.3</version>
        </dependency>

        <!--redis整合springboot(分布式版本缓存用)-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!--引入shiro依赖 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.5.3</version>
        </dependency>

        <!--引入jwt-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>

项目目录
2、工具类JWTUtils、MyByteSource

public class JWTUtils {
    private static String TOKEN = "token!Q@W3e4r";
    /**
     * 生成token
     * @param map  //传入payload
     * @return 返回token
     */
    public static String getToken(Map<String,String> map){
        JWTCreator.Builder builder = JWT.create();
        map.forEach((k,v)->{
            builder.withClaim(k,v);
        });
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DATE,5);
        builder.withExpiresAt(instance.getTime());
        return builder.sign(Algorithm.HMAC256(TOKEN)).toString();
    }
    /**
     * 验证token
     * @param token
     * @return
     */
    public static void verify(String token){
        JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
    }
    /**
     * 获取token中payload
     * @param token
     * @return
     */
    public static DecodedJWT getToken(String token){
        return JWT.require(Algorithm.HMAC256(TOKEN)).build().verify(token);
    }
}
//自定义salt实现  实现序列化接口
public class MyByteSource implements ByteSource,Serializable {

    private  byte[] bytes;
    private String cachedHex;
    private String cachedBase64;

    public MyByteSource(){

    }

    public MyByteSource(byte[] bytes) {
        this.bytes = bytes;
    }

    public MyByteSource(char[] chars) {
        this.bytes = CodecSupport.toBytes(chars);
    }

    public MyByteSource(String string) {
        this.bytes = CodecSupport.toBytes(string);
    }

    public MyByteSource(ByteSource source) {
        this.bytes = source.getBytes();
    }

    public MyByteSource(File file) {
        this.bytes = (new MyByteSource.BytesHelper()).getBytes(file);
    }

    public MyByteSource(InputStream stream) {
        this.bytes = (new MyByteSource.BytesHelper()).getBytes(stream);
    }

    public static boolean isCompatible(Object o) {
        return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
    }

    public byte[] getBytes() {
        return this.bytes;
    }

    public boolean isEmpty() {
        return this.bytes == null || this.bytes.length == 0;
    }

    public String toHex() {
        if (this.cachedHex == null) {
            this.cachedHex = Hex.encodeToString(this.getBytes());
        }

        return this.cachedHex;
    }

    public String toBase64() {
        if (this.cachedBase64 == null) {
            this.cachedBase64 = Base64.encodeToString(this.getBytes());
        }

        return this.cachedBase64;
    }

    public String toString() {
        return this.toBase64();
    }

    public int hashCode() {
        return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0;
    }

    public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (o instanceof ByteSource) {
            ByteSource bs = (ByteSource)o;
            return Arrays.equals(this.getBytes(), bs.getBytes());
        } else {
            return false;
        }
    }

    private static final class BytesHelper extends CodecSupport {
        private BytesHelper() {
        }

        public byte[] getBytes(File file) {
            return this.toBytes(file);
        }

        public byte[] getBytes(InputStream stream) {
            return this.toBytes(stream);
        }
    }

}

3、自定义的realm,分别是CodeRealm、JwtRealm、PasswordRealm

/**
 * 验证码方式验证realm
 * 这里主要用到的是验证,不用关注授权,授权会在tokenRealm中进行授权
 */
@Slf4j
public class CodeRealm extends AuthorizingRealm {

    @Autowired
    private TUserDao tUserDao;

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof CustomizedToken;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("CodeRealm权限认证开始,传递的token:{}",authenticationToken);
        //找出数据库中的对象  给定用户输入的对象做出对比
        CustomizedToken token = (CustomizedToken) authenticationToken;
        log.info("CodeRealm转换的自定义token是:{}",token);
        TUser user = tUserDao.findByUserName(token.getUsername());
        if (Objects.nonNull(user)){
            //假设发送的验证码默认是123,盐用123,散列次数1024
            Md5Hash dasda = new Md5Hash("123", "123", 1024);
            String code = dasda.toHex();
            return new SimpleAuthenticationInfo(user.getUsername(),code,new MyByteSource("123"),this.getName());
        }
        return null;
    }
}
/**
 * jwt方式验证的realm
 * 认证和授权都要进行处理
 */
@Slf4j
public class JwtRealm extends AuthorizingRealm {

    @Autowired
    private TUserDao tUserDao;

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String token = (String) principalCollection.getPrimaryPrincipal();
        DecodedJWT jwt = JWTUtils.getToken(token);
        //拿到我们放进去的用户ID
        String userName = jwt.getClaim("userName").asString();
        if (!StringUtils.hasLength(userName)){
            return null;
        }
        TUserService tUserService = (TUserService) ApplicationContextUtils.getBean("tUserService");
        //查询数据库
        TUser tUser = tUserService.findRolesByUserName(userName);
        if (!CollectionUtils.isEmpty(tUser.getRoles())){
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            tUser.getRoles().forEach(role ->{
                //角色信息
                simpleAuthorizationInfo.addRole(role.getName());
                List<TPerms> perms = tUserService.findPermsByRoleId(role.getId());
                if (!CollectionUtils.isEmpty(perms)){
                    perms.forEach(perm ->{
                        //资源信息
                        simpleAuthorizationInfo.addStringPermission(perm.getName());
                    });
                }
            });
            return simpleAuthorizationInfo;
        }
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
        log.info("jwtRealm中关于身份验证的方法执行...传递的token是:{}",authenticationToken);
        String token = (String) authenticationToken.getCredentials();
        try {
            //验证token
            JWTUtils.verify(token);
        }catch (SignatureVerificationException e){
            log.info("签名不一致异常");
            throw new AuthenticationException();
        }catch (TokenExpiredException e){
            log.info("令牌过期异常");
            throw new AuthenticationException();
        }catch (AlgorithmMismatchException e){
            log.info("算法不匹配异常");
            throw new AuthenticationException();
        }catch (InvalidClaimException e){
            log.info("失效的payload异常");
            throw new AuthenticationException();
        }
        //不报错,说明token是有效的,那么直接进行认证逻辑
        return new SimpleAuthenticationInfo(token,token,this.getName());
    }
}
/**
 * 账号密码验证realm
 * 这里主要用到的是验证,不用关注授权,授权会在tokenRealm中进行授权
 */
@Slf4j
public class PasswordRealm extends AuthorizingRealm {

    @Autowired
    private TUserDao tUserDao;

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof CustomizedToken;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("PasswordRealm权限认证开始,传递的token:{}",authenticationToken);
        //找出数据库中的对象  给定用户输入的对象做出对比
        CustomizedToken token = (CustomizedToken) authenticationToken;
        log.info("PasswordRealm转换的自定义token是:{}",token);
        TUser user = tUserDao.findByUserName(token.getUsername());
        if (Objects.nonNull(user)){
            return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),new MyByteSource(user.getSalt()),this.getName());
        }
        return null;
    }
}

4、重写的UsernamePasswordToken

@Data
public class CustomizedToken extends UsernamePasswordToken {
    /**
     * 登录类型
     * Password是账号密码,Code是验证码
     */
    public String loginType;

    public CustomizedToken(final String username, final String password, final String loginType) {
        super(username, password);
        this.loginType = loginType;
    }


}

5、JwtFilter最主要的目的是:当请求权限资源时,构造出主体subject执行login()进行认证和授权

/**
 * JwtFilter最主要的目的是:当请求权限资源时,构造出主体subject执行login()进行认证和授权
 */
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {


    /**
     * 执行登录认证
     * @param request ServletRequest
     * @param response ServletResponse
     * @param mappedValue mappedValue
     * @return 是否成功
     */
    @Override
    //这个方法叫做  尝试进行登录的操作,如果token存在,那么进行提交登录,如果不存在说明可能是正在进行登录或者做其它的事情 直接放过即可
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        try {
            executeLogin(request, response);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 执行登录
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response){
        log.info("进入JwtFilter类中...");
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("token");
        log.info("获取到的token是:{}",token);
        // 判断token是否存在
        if (!StringUtils.hasLength(token)) {
            return false;
        }
        try{
            JwtToken jwtToken = new JwtToken(token);
            log.info("提交UserModularRealmAuthenticator决定由哪个realm执行操作...");
            getSubject(request, response).login(jwtToken);
        } catch (AuthenticationException e){
            log.info("捕获到身份认证异常");
            return false;
        }
        return true;
    }



    /**
     * 对跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }
}

6、UserModularRealmAuthenticator,根据AuthenticationToken类型,判断我们使用哪个realm进行认证授权

/**
 * 根据AuthenticationToken类型,判断我们使用哪个realm进行认证授权
 */
@Slf4j
public class UserModularRealmAuthenticator extends ModularRealmAuthenticator {

    @Override
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("UserModularRealmAuthenticator:method doAuthenticate() 执行 ");
        // 判断getRealms()是否返回为空
        assertRealmsConfigured();

        //获取所有Realm
        Collection<Realm> realms = getRealms();
        // 放登录类型对应的所有Realm集合
        Realm userRealm = null;
        if (authenticationToken instanceof JwtToken){
            JwtToken jwtToken = (JwtToken) authenticationToken;
            //传进来的是Jwttoken,说明访问的是权限资源
            for (Realm realm : realms){
                String realmName = realm.getName();
                if (realmName.contains("Jwt")){
                    userRealm = realm;
                    break;
                }
            }
            return doSingleRealmAuthentication(userRealm, jwtToken);
        }else {
            //访问类型是公开类型
            CustomizedToken customizedToken = (CustomizedToken) authenticationToken;
            //获取登录类型
            String loginType = customizedToken.getLoginType();
            for (Realm realm : realms){
                if (realm.getName().contains(loginType)){
                    userRealm = realm;
                    break;
                }
            }
            return doSingleRealmAuthentication(userRealm, customizedToken);
        }
    }
}

7、自定义shiro缓存管理器RedisCacheManager

public class RedisCacheManager implements CacheManager {

    //参数1:认证或者是授权缓存的统一名称
    @Override
    public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
        System.out.println(cacheName);
        return new RedisCache<K,V>(cacheName);
    }
}

8、自定义redis缓存的实现RedisCache

public class RedisCache<k,v> implements Cache<k,v> {

    private String cacheName;

    public RedisCache() {
    }

    public RedisCache(String cacheName) {
        this.cacheName = cacheName;
    }

    @Override
    public v get(k k) throws CacheException {
        System.out.println("get key:"+k);
        return (v) getRedisTemplate().opsForHash().get(this.cacheName,k.toString());
    }

    @Override
    public v put(k k, v v) throws CacheException {
        System.out.println("put key: "+k);
        System.out.println("put value:"+v);
        getRedisTemplate().opsForHash().put(this.cacheName,k.toString(),v);
        return null;
    }

    @Override
    public v remove(k k) throws CacheException {
        System.out.println("=============remove=============");
        return (v) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
    }

    @Override
    public void clear() throws CacheException {
        System.out.println("=============clear==============");
        getRedisTemplate().delete(this.cacheName);
    }

    @Override
    public int size() {
        return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
    }

    @Override
    public Set<k> keys() {
        return getRedisTemplate().opsForHash().keys(this.cacheName);
    }

    @Override
    public Collection<v> values() {
        return getRedisTemplate().opsForHash().values(this.cacheName);
    }

    private RedisTemplate getRedisTemplate(){
        RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

9、上面基本把需要准备的都准备好了,剩下就是shiro的配置类

/**
 * shiro配置类
 */
@Configuration
public class ShiroConfig {


    /**
     * 密码登录时指定匹配器
     * @return HashedCredentialsMatcher
     */
    @Bean("hashedCredentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        // 设置哈希算法名称
        matcher.setHashAlgorithmName("MD5");
        // 设置哈希迭代次数
        matcher.setHashIterations(1024);
        // 设置存储凭证十六进制编码
        matcher.setStoredCredentialsHexEncoded(true);
        return matcher;
    }

    /**
     * 如果需要密码匹配器则需要进行指定
     * 只关注认证逻辑
     * 密码登录Realm
     * @return PasswordRealm
     */
    @Bean
    public PasswordRealm passwordRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher) {
        PasswordRealm passwordRealm = new PasswordRealm();
        passwordRealm.setCredentialsMatcher(matcher);
        return passwordRealm;
    }

    /**
     * 如果需要密码匹配器则需要进行指定
     * 只关注认证逻辑
     * 密码登录Realm
     * @return PasswordRealm
     */
    @Bean
    public CodeRealm codeRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher){
        CodeRealm codeRealm=new CodeRealm();
        codeRealm.setCredentialsMatcher(matcher);
        return codeRealm;
    }

    /**
     * jwtRealm
     * token方式验证,认证和授权都要关注
     * @return JwtRealm
     */
    @Bean
    public JwtRealm jwtRealm() {
        JwtRealm jwtRealm = new JwtRealm();
        //开启缓存管理器(EhCache适合单机版,数据缓存到服务应用内存中)
//        jwtRealm.setCachingEnabled(true);
//        jwtRealm.setAuthorizationCachingEnabled(true);
//        jwtRealm.setAuthorizationCachingEnabled(true);
//        jwtRealm.setCacheManager(new EhCacheManager());

        //开启缓存管理(redis适合集群版,数据缓存到redis)
        jwtRealm.setCacheManager(new RedisCacheManager());
        jwtRealm.setCachingEnabled(true);//开启全局缓存
        jwtRealm.setAuthenticationCachingEnabled(true);//认证认证缓存
        jwtRealm.setAuthenticationCacheName("authenticationCache");
        jwtRealm.setAuthorizationCachingEnabled(true);//开启授权缓存
        jwtRealm.setAuthorizationCacheName("authorizationCache");
        return jwtRealm;
    }

    /**
     * 1.创建shiroFilter
     * 主要负责资源权限的配置
     * 配置认证和授权过滤器
     * @param defaultWebSecurityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("shiroSecurityManager")DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

        //配置系统受限资源
        //配置系统公告资源
        Map<String,String> filterMap = new HashMap<String,String>();
        filterMap.put("/tUser/login.do","anon");//anon 设置为公共资源  放行资源放在下面
        filterMap.put("/tUser/loginByCode.do","anon");//anon 设置为公共资源  放行资源放在下面
        filterMap.put("/**","jwt");
        //将权限的配置告诉给shiro
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);

        //把我们自定义的jwt过滤器添加到shiro
        LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>(1);
        filtersMap.put("jwt",new JwtFilter());
        shiroFilterFactoryBean.setFilters(filtersMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 2.创建安全管理器
     * 把我们自定义的realm和重写的UserModularRealmAuthenticator添加到DefaultWebSecurityManager
     */
    @Bean
    public DefaultWebSecurityManager shiroSecurityManager(
            @Qualifier("codeRealm")CodeRealm codeRealm,
            @Qualifier("passwordRealm") PasswordRealm passwordRealm,
            @Qualifier("jwtRealm") JwtRealm jwtRealm,
            @Qualifier("userModularRealmAuthenticator") UserModularRealmAuthenticator userModularRealmAuthenticator) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm
        securityManager.setAuthenticator(userModularRealmAuthenticator);
        List<Realm> realms = new ArrayList<>();
        // 添加多个realm
        realms.add(passwordRealm);
        realms.add(jwtRealm);
        realms.add(codeRealm);
        securityManager.setRealms(realms);

        /*
         * 关闭shiro自带的session,详情见文档
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);

        return securityManager;
    }

    /**
     * 重写的ModularRealmAuthenticator中的doAuthenticate方法
     * 目的是根据传入的AuthenticationToken类型来判断使用我们哪个我自定义的realm进行验证授权
     * @return
     */
    @Bean
    public UserModularRealmAuthenticator userModularRealmAuthenticator() {
        UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator();
        modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
        return modularRealmAuthenticator;
    }

    /**
     * 以下Bean开启shiro权限注解
     *
     * @return DefaultAdvisorAutoProxyCreator
     */
    @Bean
    public static DefaultAdvisorAutoProxyCreator creator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
}

10、写好账号密码和验证码登录的两个接口

//用户密码登录
    @CrossOrigin(origins = "*", maxAge = 3600)
    @RequestMapping(value = "/login.do",method = RequestMethod.POST)
    public RetObj login(@Param("tUser") String tUser){
        TUser tUserVo = JSONObject.parseObject(tUser, TUser.class);
        RetObj ret = new RetObj();
        if (Objects.isNull(tUserVo)){
            ret.Fail("信息不能为空");
            return ret;
        }
        try {
            ret = tUserService.login(tUserVo.getUsername(), tUserVo.getPassword());
        }catch (Exception e){
            log.error("更新信息异常:"+ ExceptionUtil.getStackStr(e));
        }
        return ret;
    }

    //验证码登录
    @CrossOrigin(origins = "*", maxAge = 3600)
    @RequestMapping(value = "/loginByCode.do",method = RequestMethod.POST)
    public RetObj loginByCode(@Param("tUser") String tUser){
        TUser tUserVo = JSONObject.parseObject(tUser, TUser.class);
        RetObj ret = new RetObj();
        if (Objects.isNull(tUserVo)){
            ret.Fail("信息不能为空");
            return ret;
        }
        try {
            ret = tUserService.loginByCode(tUserVo.getUsername(), tUserVo.getPassword());
        }catch (Exception e){
            log.error("更新信息异常:"+ ExceptionUtil.getStackStr(e));
        }
        return ret;
    }

11、对应的业务层代码

@Override
    public RetObj login(String username, String password) {
        RetObj retObj = new RetObj();
        Subject subject = SecurityUtils.getSubject();
        CustomizedToken customizedToken = new CustomizedToken(username, password, "Password");

        try {
            subject.login(customizedToken);//用户登录
            System.out.println("登录成功~~");
        } catch (UnknownAccountException e) {
            retObj.Fail("用户名错误");
            return retObj;
        }catch (IncorrectCredentialsException e){
            retObj.Fail("密码错误");
            return retObj;
        }
        TUser user = new TUser();
        user.setUsername(username);
        List<TUser> tUsers = selectList(user, null, null);

        if (!CollectionUtils.isEmpty(tUsers)){
            Map<String,String> map = new HashMap<>();
            map.put("userId",String.valueOf(tUsers.get(0).getId()));
            map.put("userName",String.valueOf(tUsers.get(0).getUsername()));
            String token = JWTUtils.getToken(map);
            retObj.setMsg(token);
            return retObj;
        }
        return retObj;
    }

    @Override
    public RetObj loginByCode(String username, String code) {
        RetObj retObj = new RetObj();
        Subject subject = SecurityUtils.getSubject();
        CustomizedToken customizedToken = new CustomizedToken(username, code, "Code");

        try {
            subject.login(customizedToken);//用户登录
            System.out.println("登录成功~~");
        } catch (UnknownAccountException e) {
            retObj.Fail("用户名错误");
            return retObj;
        }catch (IncorrectCredentialsException e){
            retObj.Fail("密码错误");
            return retObj;
        }
        TUser user = new TUser();
        user.setUsername(username);
        List<TUser> tUsers = selectList(user, null, null);

        if (!CollectionUtils.isEmpty(tUsers)){
            Map<String,String> map = new HashMap<>();
            map.put("userId",String.valueOf(tUsers.get(0).getId()));
            map.put("userName",String.valueOf(tUsers.get(0).getUsername()));
            String token = JWTUtils.getToken(map);
            retObj.setMsg(token);
            return retObj;
        }
        return retObj;
    }

12、分别进行登录测试,都能拿到正确的token
账号密码登录返回
验证码登录返回
13、加一个全局异常捕获,主要是捕获shiro验证的报错从而返回提示信息

@ControllerAdvice
public class NoPermissionException {
    @ResponseBody
    @ExceptionHandler(UnauthorizedException.class)
    public RetObj handleShiroException(Exception ex) {
        RetObj retObj = new RetObj();
        retObj.Fail("无权限");
        return retObj;
    }

    @ResponseBody
    @ExceptionHandler(AuthorizationException.class)
    public RetObj authorizationException(Exception ex) {
        RetObj retObj = new RetObj();
        retObj.Fail("身份验证失败");
        return retObj;
    }

    @ResponseBody
    @ExceptionHandler(UnauthenticatedException.class)
    public RetObj unauthenticatedException(Exception ex) {
        RetObj retObj = new RetObj();
        retObj.Fail("请先登录");
        return retObj;
    }

}

14、对有权限的接口进行请求
14.1不带token请求
14.2请求没有授权的资源

14.3请求授权的资源

总结
当回头看代码时,整个思路变得特别清晰,我们再回顾一次整个项目,当前端请求访问授权资源时,会被shiro拦截,代码将执行到JwtFilter(重写了BasicHttpAuthenticationFilter中的isAccessAllowedexecuteLogin方法)类中的isAccessAllowed方法,然后执行其中executeLogin方法,初始化JwtToken(重新实现了AuthenticationToken)然后构造出主体subject执行login()进行认证和授权,这时候代码将执行到UserModularRealmAuthenticator(重写了ModularRealmAuthenticator的doAuthenticate方法)类中的doAuthenticate方法,然后根据AuthenticationToken类型,使用指定的realm进行认证授权,执行JwtRealm(重新了AuthorizingRealmdoGetAuthenticationInfodoGetAuthorizationInfo方法)中的认证方法doGetAuthenticationInfo和授权方法doGetAuthorizationInfo,整个请求的流程就结束了,启动了redis缓存,只有第一次会访问数据库,后面都是访问redis缓存。

  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值