栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring boot整合shiro+jwt实现前后端分离

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring boot整合shiro+jwt实现前后端分离

本文实例为大家分享了Spring boot整合shiro+jwt实现前后端分离的具体代码,供大家参考,具体内容如下

这里内容很少很多都为贴的代码,具体内容我经过了看源码和帖子加了注释。帖子就没用太多的内容

先下载shiro和jwt的jar包



  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());
  }
 
  
  @Override
  public boolean supports(AuthenticationToken token) {
    return token instanceof JwtToken;
  }
 
  
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)throws AuthenticationException {
    
      String JWTtoken = ((JwtToken) token).getToken();
    
Claims claims = JWTDecodeUtil.parseJWT(JWTtoken, SecretKey.JWTKey);
      
    
      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;
    
    Object principal = principals.getPrimaryPrincipal();
    
    AutoUserModel user = (AutoUserModel) principal;
    List roleModels = loginService.selectRoleDetails(user.getId());
    try {
    
    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;
 
 
 

public class JWTCredentialsMatcher implements CredentialsMatcher {
 
  private Logger logger = Logger.getLogger(JWTCredentialsMatcher.class);
 
  
  @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
  @Bean
  protected SessionStorageevaluator sessionStorageevaluator(){
    DefaultWebSessionStorageevaluator sessionStorageevaluator = new DefaultWebSessionStorageevaluator();
    sessionStorageevaluator.setSessionStorageEnabled(false);
    return sessionStorageevaluator;
  }
 
 
  @Bean("securityManager")
  public SecurityManager securityManager(UserRealm userRealm) {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(userRealm);
    
    DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
    DefaultSessionStorageevaluator defaultSessionStorageevaluator = new DefaultSessionStorageevaluator();
    defaultSessionStorageevaluator.setSessionStorageEnabled(false);
    subjectDAO.setSessionStorageevaluator(defaultSessionStorageevaluator);
    securityManager.setSubjectDAO(subjectDAO);
 
    return securityManager;
  }
  
  @Bean("userRealm")
  public UserRealm shiroRealm() {
    UserRealm shiroRealm = new UserRealm();
    return shiroRealm;
  }
 
 
  //自动创建代理,没有这个鉴权可能会出错
  @Bean
  public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
    DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
    autoProxyCreator.setProxyTargetClass(true);
    return autoProxyCreator;
  }
  
  @Bean
  public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
    AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
    authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
    return authorizationAttributeSourceAdvisor;
  }

到这里shiro配置就全部完成了

下面开始配置jwt:

首先我们需要重写  AuthenticationToken接口 此接口的作用

负责把shiro中username,password生成用于验证的token的封装类

所有我们需要去实现这个接口,封装我们自己生成的JWT生成的token

import org.apache.shiro.authc.AuthenticationToken;
 

public class JwtToken implements AuthenticationToken {
 
  private String token;
 
  public JwtToken(String token) {
    this.token = token;
  }
 
  public String getToken() {
    return token;
  }
 
  public void setToken(String token) {
    this.token = token;
  }
 
  @Override
  public Object getPrincipal() {
    return null;
  }
 
  @Override
  public Object getCredentials() {
    return null;
  }
}

因为我们是前后端分离项目所有我们不存在session验证之类的 所有每次请求都需要携带token,所以每次请求我们都需要去验证token的真实性,所有我们需要去实现  BasicHttpAuthenticationFilter过滤器  

BasicHttpAuthenticationFilter继承 AuthenticatingFilter 过滤器其能够自动地进行基于所述传入请求的认证尝试。此实现是每个基本HTTP身份验证规范的Java实现  , 通过此过滤器得到HTTP请求资源获取Authorization传递过来的token参数   获取subject对象进行身份验证

代码如下:

import com.alibaba.fastjson.JSONObject;
import com.serverprovider.config.shiro.jwt.JwtToken;
import com.util.Util.utilTime;
import org.apache.log4j.Logger;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;
 

public class JwtFilter extends BasicHttpAuthenticationFilter {
 
  Logger logger = Logger.getLogger(JwtFilter.class);
 
  
  @Override
  protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    HttpServletRequest httpServletRequest = (HttpServletRequest) request;
    String requestURI = httpServletRequest.getRequestURI();
    if (requestURI.equals("/user/login/verifyUser") || requestURI.equals("/user/register")) {
      return true;
    } else {
      try {
 executeLogin(request, response);
 return true;
      } catch (Exception e) {
 e.printStackTrace();
 return false;
      }
    }
  }
 
 
 
  
  @Override
  protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
    HttpServletResponse httpServletResponse = (HttpServletResponse) response;
    try{
      HttpServletRequest httpServletRequest = (HttpServletRequest) request;
      String token = httpServletRequest.getHeader("Authorization");
      JwtToken jwtToken = new JwtToken(token);
      // 提交给realm进行登入,如果错误他会抛出异常并被捕获
      Subject subject = getSubject(request, response);
      subject.login(jwtToken);
      logger.info("JWT验证用户信息成功");
      // 如果没有抛出异常则代表登入成功,返回true
      return true;
      }catch (Exception e){
      
      JSonObject responseJSONObject = new JSonObject();
      responseJSONObject.put("result","401");
      responseJSONObject.put("resultCode","token无效,请重新获取。");
      responseJSONObject.put("resultData","null");
      responseJSONObject.put("resultTime", utilTime.StringDate());
      PrintWriter out = null;
      httpServletResponse.setCharacterEncoding("UTF-8");
      httpServletResponse.setContentType("application/json; charset=utf-8");
      logger.info("返回是");
      logger.info(responseJSONObject.toString());
      out = httpServletResponse.getWriter();
      out.append(responseJSONObject.toString());
    }
  return false;
  }
 
 
 
  
  @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);
  }
 
}

贴上 加密解密校验的工具类

import com.spring.common.auto.autoUser.extend.AutoModelExtend;
import com.util.ReturnUtil.SecretKey;
import io.jsonwebtoken.*;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;;
 

 
public class JwtUtil {
 
 
  
  public static String createJWT(long ttlMillis, AutoModelExtend user) {
    //指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
    SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
    //生成JWT的时间
    long nowMillis = System.currentTimeMillis();
    Date now = new Date(nowMillis);
 
    //创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
    Map map = new HashMap();
    map.put("id", user.getId());
    map.put("username", user.getAuto_username());
    map.put("password", user.getAuto_password());
 
    //生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,
    // 切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
    String key = SecretKey.JWTKey;
    //生成签发人
    String subject = user.getAuto_username();
 
 
    //下面就是在为payload添加各种标准声明和私有声明了
    //这里其实就是new一个JwtBuilder,设置jwt的body
    JwtBuilder builder = Jwts.builder()
 //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
 .setClaims(map)
 //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
 .setId(UUID.randomUUID().toString())
 //iat: jwt的签发时间
 .setIssuedAt(now)
 //代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。
 .setSubject(subject)
 //设置签名使用的签名算法和签名使用的秘钥
 .signWith(signatureAlgorithm, key);
    if (ttlMillis >= 0) {
      long expMillis = nowMillis + ttlMillis;
      Date exp = new Date(expMillis);
      //设置过期时间
      builder.setExpiration(exp);
    }
    return builder.compact();
  }
 
 
  
  public static Boolean isVerify(String token, AutoModelExtend userModelExtend) {
    try {
      //得到DefaultJwtParser
      Claims claims = Jwts.parser()
   //设置签名的秘钥
   .setSigningKey(SecretKey.JWTKey)
   //设置需要解析的jwt
   .parseClaimsJws(token).getBody();
 
      if (claims.get("password").equals(userModelExtend.getAuto_password())) {
 return true;
      }
    } catch (Exception exception) {
      return false;
    }
    return null;
 
  }
 
  public static Claims parseJWT(String token, String secret) {
    //得到DefaultJwtParser
    Claims claims = Jwts.parser()
 //设置签名的秘钥
 .setSigningKey(secret)
 //设置需要解析的jwt
 .parseClaimsJws(token).getBody();
    return claims;
  }
}

到这里shiro jwt整合就完成了。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持考高分网。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/135833.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号