怎么在Springboot中使用shiro和jwt实现前后端分离-成都快上网建站

怎么在Springboot中使用shiro和jwt实现前后端分离

这篇文章给大家介绍怎么在Spring boot中使用shiro和jwt实现前后端分离,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

在北京等地区,都构建了全面的区域性战略布局,加强发展的系统性、市场前瞻性、产品创新能力,以专注、极致的服务理念,为客户提供网站设计制作、网站制作 网站设计制作按需网站建设,公司网站建设,企业网站建设,品牌网站设计,成都全网营销,成都外贸网站建设公司,北京网站建设费用合理。



  org.apache.shiro
  shiro-spring
  1.4.0


  org.apache.shiro
  shiro-ehcache
  1.4.0




  com.auth0
  java-jwt
  3.4.0



   io.jsonwebtoken
   jjwt
   0.9.0

创建shiro的自定义的Realm

代码如下:

package com.serverprovider.config.shiro.userRealm;
 
 
import com.spring.common.auto.autoUser.AutoUserModel;
import com.spring.common.auto.autoUser.extend.AutoModelExtend;
import com.serverprovider.config.shiro.jwt.JWTCredentialsMatcher;
import com.serverprovider.config.shiro.jwt.JwtToken;
import com.serverprovider.service.loginService.LoginServiceImpl;
import com.util.redis.RedisUtil;
import com.util.ReturnUtil.SecretKey;
import com.util.encryption.JWTDecodeUtil;
import io.jsonwebtoken.Claims;
import org.apache.log4j.Logger;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ExceptionHandler;
 
import java.util.HashSet;
import java.util.List;
import java.util.Set;
 
public class UserRealm extends AuthorizingRealm {
 
  private Logger logger = Logger.getLogger(UserRealm.class);
 
 
  @Autowired private LoginServiceImpl loginService;
 
 
  public UserRealm(){
    //这里使用我们自定义的Matcher验证接口
    this.setCredentialsMatcher(new JWTCredentialsMatcher());
  }
 
  /**
   * 必须重写此方法,不然Shiro会报错
   */
  @Override
  public boolean supports(AuthenticationToken token) {
    return token instanceof JwtToken;
  }
 
  /**
   * shiro 身份验证
   * @param token
   * @return boolean
   * @throws AuthenticationException 抛出的异常将有统一的异常处理返回给前端
   *
   */
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)throws AuthenticationException {
    /**
     * AuthenticationToken
     * JwtToken重写了AuthenticationToken接口 并创建了一个接口token的变量
     *  因为在filter我们将token存入了JwtToken的token变量中
     *  所以这里直接getToken()就可以获取前端传递的token值
     */
      String JWTtoken = ((JwtToken) token).getToken();
    /**
     * Claims对象它最终是一个JSON格式的对象,任何值都可以添加到其中
     * token解密 转换成Claims对象
     */
       Claims claims = JWTDecodeUtil.parseJWT(JWTtoken, SecretKey.JWTKey);
      
    /**
     *  根据JwtUtil加密方法加入的参数获取数据
     *  查询数据库获得对象
     *  如为空:抛出异常
     *  如验证失败抛出 AuthorizationException
     */
      String username = claims.getSubject();
      String password = (String) claims.get("password");
      AutoModelExtend principal = loginService.selectLoginModel(username,password);
      return new SimpleAuthenticationInfo(principal, JWTtoken,"userRealm");
  }
 
 
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    SimpleAuthorizationInfo info = null;
    /**
     * PrincipalCollection对象
     * 文档里面描述:返回从指定的Realm 仅作为Collection 返回的单个Subject的对象,如果没有来自该领域的任何对象,则返回空的Collection。
     * 在登录接口放入权限注解返回的错误信息:Subject.login(AuthenticationToken)或SecurityManager启用'Remember Me'功能后成功自动获取这些标识主体
     * 当调用Subject.login()方法成功后 PrincipalCollection会自动获得该对象 如没有认证过或认证失败则返回空的Collection并抛出异常
     * getPrimaryPrincipal():返回在应用程序范围内使用的主要对象,以唯一标识拥有帐户。
     */
    Object principal = principals.getPrimaryPrincipal();
    /**
     * 得到身份对象
     * 查询该用户的权限信息
     */
    AutoUserModel user = (AutoUserModel) principal;
    List roleModels = loginService.selectRoleDetails(user.getId());
    try {
    /**
     * 创建一个Set,来放置用户拥有的权限
     * 创建 SimpleAuthorizationInfo, 并将办好权限列表的Set放入.
     */
    Set rolesSet = new HashSet();
    for (String role : roleModels) {
      rolesSet.add(role);
    }
    info = new SimpleAuthorizationInfo();
    info.setStringPermissions(rolesSet);  // 放入权限信息
  }catch (Exception e){
    throw new AuthenticationException("授权失败!");
  }
    return info;
  }
}

这个授权方法遇到的坑比较少,就是在最终验证的时候网上很照抄过来的帖子一点都没有验证就粘贴赋值,在这里严重吐槽。

在使用jwt最为token而取消shiro传统的session时候,我们的需要重写shiro的验证接口   CredentialsMatcher,在 自定义的realm

中我们加入我们重写的验证方法,在调用SimpleAuthenticationInfo()方法进行验证的时候,shiro就会使用重写的验证接口。

此处为大坑。

贴上代码如下:

import com.spring.common.auto.autoUser.extend.AutoModelExtend;
import org.apache.log4j.Logger;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.CredentialsMatcher;
 
 
 
/**
 * CredentialsMatcher
 * 接口由类实现,该类可以确定是否提供了AuthenticationToken凭证与系统中存储的相应帐户的凭证相匹配。
 * Shiro 加密匹配 重写匹配方法CredentialsMatcher 使用JWTUtil 匹配方式
 */
public class JWTCredentialsMatcher implements CredentialsMatcher {
 
  private Logger logger = Logger.getLogger(JWTCredentialsMatcher.class);
 
  /**
   * Matcher中直接调用工具包中的verify方法即可
   */
  @Override
  public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo authenticationInfo) {
    String token = (String) ((JwtToken)authenticationToken).getToken();
    AutoModelExtend user = (AutoModelExtend)authenticationInfo.getPrincipals().getPrimaryPrincipal();
    //得到DefaultJwtParser
    Boolean verify = JwtUtil.isVerify(token, user);
    logger.info("JWT密码效验结果="+verify);
    return verify;
  }
}

shiro的配置项  ShiroConfiguration代码如下:

import com.serverprovider.config.shiro.shiroSysFile.JwtFilter;
import com.serverprovider.config.shiro.userRealm.UserRealm;
import org.apache.log4j.Logger;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.mgt.SessionStorageEvaluator;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.DefaultWebSessionStorageEvaluator;
 
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
import javax.servlet.Filter;
import java.util.*;
 
@Configuration
public class ShiroConfiguration {
 
  private Logger logger = Logger.getLogger(ShiroConfiguration.class);
 
 
 
  @Bean("shiroFilter")
  public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    //拦截器
    Map filterChainDefinitionMap = new LinkedHashMap();
    // 配置不会被拦截的链接 顺序判断
    filterChainDefinitionMap.put("/login/**", "anon");
    // 添加自己的过滤器并且取名为jwt
    Map filterMap = new HashMap();
    filterMap.put("jwt", new JwtFilter());
    shiroFilterFactoryBean.setFilters(filterMap);
    //