关于JWT(Json Web Token)

在这里插入图片描述
在这里插入图片描述
https://jwt.io/
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
https://jwt.io/libraries?language=Java
在这里插入图片描述

		<!-- JJWT(Java JWT) -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

在这里插入图片描述

package cn.tedu.csmallpassport;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.jupiter.api.Test;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JwtTest {
    String secretKey = "kU4jrFA3iuI5jn25u743kfDs7a8pFEwS54hm";  // 盐值

    @Test
    public void generate() {

        Date date = new Date(System.currentTimeMillis() + 5 * 60 * 1000);
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", "1233");
        claims.put("username", "zhangsan");
        String jwt = Jwts.builder()
        // Header(头部信息):声明算法与Token的类型
                .setHeaderParam("alg", "HS256")
                .setHeaderParam("typ", "JWT")
        // Payload(载荷):数据,表现为Claims
                .setClaims(claims)
                .setExpiration(date)   // 过期时间
        // Signature:验证签名
                .signWith(SignatureAlgorithm.HS256, secretKey)
        // 完成
        .compact();
        System.out.println(jwt);  // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEyMzMiLCJleHAiOjE2ODA3NTA4MzQsInVzZXJuYW1lIjoiemhhbmdzYW4ifQ.7RNj9hWPX8ebFcYrBooFNAlKgc3AIV_WU_qF0wsbltQ
    }

    @Test
    public void parse() {
        try {
            String jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEyMzMiLCJleHAiOjE2ODA3NTA4MzQsInVzZXJuYW1lIjoiemhhbmdzYW4ifQ.7RNj9hWPX8ebFcYrBooFNAlKgc3AIV_WU_qF0wsbltQ";

            Claims claims = Jwts.parser()
                    .setSigningKey(secretKey)
                    .parseClaimsJws(jwt)
                    .getBody();

            Long id = claims.get("id", Long.class);
            String username = claims.get("username", String.class);

            System.out.println("id = " + id);
            System.out.println("username = " + username);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在项目中使用JWT识别客户端

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

验证登录成功后响应JWT

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

/**
     * 验证管理员登录
     * @param adminLoginDTO 管理员的登录信息,至少封装用户名与密码原文
     * @return 验证登录通过后的JWT
     */
    String login(AdminLoginDTO adminLoginDTO);

在这里插入图片描述

@Override
public String login(AdminLoginDTO adminLoginDTO) {
    log.debug("开始处理【管理员登录】的业务,参数:{}", adminLoginDTO);

    // 创建认证信息对象
    Authentication authentication = new UsernamePasswordAuthenticationToken(
            adminLoginDTO.getUsername(), adminLoginDTO.getPassword());
    // 调用认证管理器执行认证
    Authentication authenticationResult
            = authenticationManager.authenticate(authentication);
    log.debug("验证登录成功,返回的Authentication为:{}", authenticationResult);
    // 如果没有出现异常,则表示验证登录成功,需要将认证信息存入到Security上下文
    // log.debug("即将向SecurityContext中存入Authentication");
    // SecurityContext securityContext = SecurityContextHolder.getContext();
    // securityContext.setAuthentication(authenticationResult);

    // ========== 以下是新增的代码片段 ==========
    
    // 处理验证登录成功后的结果中的当事人
    Object principal = authenticationResult.getPrincipal();
    log.debug("获取验证登录成功后的结果中的当事人:{}", principal);
    AdminDetails adminDetails = (AdminDetails) principal;

    // 需要写入到JWT中的数据
    Map<String, Object> claims = new HashMap<>();
    claims.put("id", adminDetails.getId());
    claims.put("username", adminDetails.getUsername());
    log.debug("即将生成JWT数据,包含的账号信息:{}", claims);

    // 生成JWT,并返回JWT
    String secretKey = "kU4jrFA3iuI5jn25u743kfDs7a8pFEwS54hm";
    Date exp = new Date(System.currentTimeMillis() + 10 * 24 * 60 * 60 * 1000);
    String jwt = Jwts.builder()
            .setHeaderParam("alg", "HS256")
            .setHeaderParam("typ", "JWT")
            .setClaims(claims)
            .setExpiration(exp)
            .signWith(SignatureAlgorithm.HS256, secretKey)
            .compact();
    log.debug("生成了JWT数据,并将返回此JWT数据:{}", jwt);
    return jwt;
}

在这里插入图片描述

@PostMapping("/login")
    public JsonResult login(AdminLoginDTO adminLoginDTO) {
        log.debug("开始处理【管理员登录】的请求,参数:{}", adminLoginDTO);
        String jwt = adminService.login(adminLoginDTO);
        return JsonResult.ok(jwt);
    }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

解析客户端携带的JWT(使用过滤器)

在这里插入图片描述
在这里插入图片描述

在项目的根包下创建filter.JwtAuthorizationFilter类,继承自OncePerRequestFilter抽象类(将间接的实现Filter接口),并在类上添加组件注解:

package cn.tedu.csmall.passport.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@Component
public class JwtAuthorizationFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {
    }

}

在处理过程中,首先,需要尝试接收客户端携带的JWT:

@Slf4j
@Component
public class JwtAuthorizationFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        // 根据业内惯用的做法,客户端应该将JWT保存在请求头(Request Header)中的名为Authorization的属性名
        String jwt = httpServletRequest.getHeader("Authorization");
        log.debug("尝试接收客户端携带的JWT数据,JWT数据:{}", jwt);
        filterChain.doFilter(request, response);
    }
}

在这里插入图片描述

@Slf4j
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    // 新增代码
    @Autowired
    private JwtAuthorizationFilter jwtAuthorizationFilter;
    
    // 暂不关心其它代码
    
}

在这里插入图片描述

// 将自定义的JWT过滤器添加在Spring Security的UsernamePasswordAuthenticationFilter之前
http.addFilterBefore(jwtAuthorizationFilter, 
                     UsernamePasswordAuthenticationFilter.class);

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

package cn.tedu.csmall.passport.filter;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;

/**
 * <p>处理JWT的过滤器类</p>
 *
 * <p>此过滤器类的主要职责:</p>
 * <ul>
 *     <li>尝试接收客户端携带的JWT</li>
 *     <li>尝试解析接收到的JWT</li>
 *     <li>将解析成功后得到的结果创建为Authentication并存入到SecurityContext中</li>
 * </ul>
 *
 * @author java@tedu.cn
 * @version 0.0.1
 */
@Slf4j
@Component
public class JwtAuthorizationFilter extends OncePerRequestFilter {

    public static final int JWT_MIN_LENGTH = 113;

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {
        // 根据业内惯用的做法,客户端应该将JWT保存在请求头(Request Header)中的名为Authorization的属性名
        String jwt = request.getHeader("Authorization");
        log.debug("尝试接收客户端携带的JWT数据,JWT:{}", jwt);

        // 判断客户端是否携带了基本有效的JWT
        if (!StringUtils.hasText(jwt) || jwt.length() < JWT_MIN_LENGTH) {
            // 客户端没有携带有铲的JWT,则“放行”,交由后续的组件继续处理
            filterChain.doFilter(request, response);
            // 【重要】终止当前方法的执行,不执行接下来的代码
            return;
        }

        // TODO:1-声明secretKey不合理,应该集中管理
        // TODO:2-解析JWT时可能出现异常,需要处理
        // 客户端携带了基本有效的JWT,则尝试解析JWT
        String secretKey = "kU4jrFA3iuI5jn25u743kfDs7a8pFEwS54hm";
        Claims claims = Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
        Long id = claims.get("id", Long.class);
        String username = claims.get("username", String.class);
        log.debug("从JWT中解析得到的管理员ID:{}", id);
        log.debug("从JWT中解析得到的管理员用户名:{}", username);

        // TODO:3-使用用户名的字符串作为“当事人”并不是最优解
        // TODO:4-需要调整使用真实的权限
        // 基于解析JWT的结果创建Authentication对象
        Object principal = username; // 当事人:暂时使用用户名
        Object credentials = null; // 凭证:应该为null
        Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("暂时放一个山寨的权限"));
        Authentication authentication = new UsernamePasswordAuthenticationToken(
                principal, credentials, authorities);

        // 将Authentication存入到SecurityContext中
        log.debug("向SecurityContext中存入Authentication:{}", authentication);
        SecurityContext securityContext = SecurityContextHolder.getContext();
        securityContext.setAuthentication(authentication);

        // 过滤器链继续执行,相当于“放行”
        filterChain.doFilter(request, response);
    }

}

在这里插入图片描述

关于SecurityContext中的认证信息

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

	// 清空SecurityContext,避免【此前携带JWT成功访问后,在接下来的一段时间内不携带JWT也能访问】
        SecurityContextHolder.clearContext();

在这里插入图片描述

		// 将Session策略设置为“从不使用”:STATELESS=无状态,即从不使用Session,NEVER=从不主动创建Session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

关于当事人

在这里插入图片描述
在这里插入图片描述

package cn.tedu.csmallpassport.security;

import lombok.Data;

import java.io.Serializable;

@Data
public class LoginPrincipal implements Serializable {

    private Long id;
    private String username;

}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

处理解析JWT时的异常

在这里插入图片描述
在这里插入图片描述

	ERR_JWT_EXPIRED(600, "JWT已过期"),
    ERR_JWT_SIGNATURE(601, "验证签名失败"),
    ERR_JWT_MALFORMED(602, "JWT格式错误"),

在这里插入图片描述

// 客户端携带了基本有效的JWT,则尝试解析JWT
Claims claims = null;
response.setContentType("application/json; charset=utf-8;");
try {
    claims = Jwts.parser()
            .setSigningKey(secretKey)
            .parseClaimsJws(jwt)
            .getBody();
} catch (ExpiredJwtException e) {
    String message = "您的登录信息已过期,请重新登录!";
    log.warn(message);
    JsonResult jsonResult = JsonResult.fail(ServiceCode.ERR_JWT_EXPIRED, message);
    String jsonResultString = JSON.toJSONString(jsonResult);
    PrintWriter writer = response.getWriter();
    writer.println(jsonResultString);
    writer.close();
    return;
} catch (SignatureException e) {
    String message = "非法访问!";
    log.warn(message);
    JsonResult jsonResult = JsonResult.fail(ServiceCode.ERR_JWT_SIGNATURE, message);
    String jsonResultString = JSON.toJSONString(jsonResult);
    PrintWriter writer = response.getWriter();
    writer.println(jsonResultString);
    writer.close();
    return;
} catch (MalformedJwtException e) {
    String message = "非法访问!";
    log.warn(message);
    JsonResult jsonResult = JsonResult.fail(ServiceCode.ERR_JWT_MALFORMED, message);
    String jsonResultString = JSON.toJSONString(jsonResult);
    PrintWriter writer = response.getWriter();
    writer.println(jsonResultString);
    writer.close();
    return;
} catch (Throwable e) {
    String message = "服务器忙,请稍后再次尝试!(开发过程中,如果看到此提示,请检查控制台的信息,并在JWT过滤器补充处理此异常)";
    log.warn(message);
    JsonResult jsonResult = JsonResult.fail(ServiceCode.ERR_UNKNOWN, message);
    String jsonResultString = JSON.toJSONString(jsonResult);
    PrintWriter writer = response.getWriter();
    writer.println(jsonResultString);
    writer.close();
}

处理未登录的错误

在这里插入图片描述
在这里插入图片描述

// 处理“当客户端提交请求时没有携带JWT,请求的目标却是需要通过认证的资源”的问题
http.exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPoint() {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        response.setContentType("application/json; charset=utf-8;");
        String message = "未检测到登录信息,请登录!(在开发阶段,看到此提示时,请检查客户端是否携带了有效的JWT数据)";
        log.warn(message);
        JsonResult jsonResult = JsonResult.fail(ServiceCode.ERR_UNAUTHORIZED, message);
        String jsonResultString = JSON.toJSONString(jsonResult);
        PrintWriter writer = response.getWriter();
        writer.println(jsonResultString);
        writer.close();
    }
});

客户端携带JWT提交请求

在客户端,当用户提交登录请求,且服务器端验证登录通过后,会向客户端响应“登录成功”的业务状态码及JWT数据,则客户端需要将此JWT数据保存下来,以便于后续在其它页面中可以取出,并用于提交后续的请求。

在这里插入图片描述

例如,在登录成功后:

在这里插入图片描述
在这里插入图片描述

使用axios时,需要调用create()来自定义请求头,此方法将返回一个新的axios对象,例如:

在这里插入图片描述

在这里插入图片描述

关于复杂请求的跨域访问

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

		// 此方法会配置Spring Security框架自带的CorsFilter,此过滤器会对OPTIONS请求放行
        http.cors();

在这里插入图片描述

提示:对于复杂请求的预检(提交同一个URL的OPTIONS请求)是客户端的浏览器的自主行为,并不是服务器端的要求,并且,对于同一个URL,如果预检通过,浏览器会缓存“预检通过”这个结果,并且,在后续的访问中,不再执行预检。

配置 JSON 工具,将权限改为根据数据库查询出来的,地址: https://editor.csdn.net/md/?articleId=130006898

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值