您当前的位置: 首页 >  spring

梁云亮

暂无认证

  • 2浏览

    0关注

    1211博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

SpringBoot整合JWT 三

梁云亮 发布时间:2020-04-23 00:10:28 ,浏览量:2

功能简介
  • 前后端分离
  • 跨域访问
  • 登录拦截判断
代码地址

SpringBoot整合Jwt实现前后端分离代码:下载地址一 SpringBoot整合Jwt实现前后端分离代码:下载地址二

后端代码 数据库信息
 CREATE TABLE `tb_user` (
  `id` int(45) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1002 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
第一步:创建Maven项目

    org.springframework.boot
    spring-boot-starter-logging



    org.projectlombok
    lombok
    1.18.10


    com.alibaba
    fastjson
    1.2.62



    org.springframework.boot
    spring-boot-starter



    org.springframework.boot
    spring-boot-starter-web



    org.springframework.boot
    spring-boot-starter-test
    test
    
        
            org.junit.vintage
            junit-vintage-engine
        
    



    io.jsonwebtoken
    jjwt
    0.9.1



    commons-codec
    commons-codec
    1.13


    com.auth0
    java-jwt
    3.10.2



    mysql
    mysql-connector-java
    8.0.18


    com.alibaba
    druid
    1.1.22


    org.mybatis
    mybatis
    3.5.4


    org.mybatis.spring.boot
    mybatis-spring-boot-starter
    2.1.2


    org.mybatis
    mybatis-spring
    2.0.4


    org.springframework.boot
    spring-boot-starter-jdbc


    org.mybatis.spring.boot
    mybatis-spring-boot-autoconfigure
    2.1.2




    com.github.pagehelper
    pagehelper-spring-boot-starter
    1.2.5



    org.springframework
    spring-tx
    5.2.3.RELEASE

第二步:配置文件 application.yml
server:
  port: 80
  servlet:
    context-path: /jd

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db_test?useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: root
mybatis:
  config-location: classpath:mybatis.xml
  #Mapper.xml所在的位置
  mapper-locations: classpath*:mapper/*.xml
  #Entity扫描的model包
  type-aliases-package: com.hc.bean

logging:
  config: classpath:logback.xml
mybatis.xml




    
        
        
        
    


logback.xml


    
    
    
    
    
    
    
    
    
        
            
                
                %d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger - %msg%n
            
        
    
    
    
        
        
        
        
    

第三步:工具类 Base64Util.java
@Slf4j
public class Base64Util {
    private static final String charset = "utf-8";

    // 解密
    public static String decode(String data) {
        try {
            if (null == data) {
                return null;
            }
            return new String(Base64.decodeBase64(data.getBytes(charset)), charset);
        } catch (UnsupportedEncodingException e) {
            log.error(String.format("字符串:%s,解密异常", data), e);
        }
        return null;
    }

    // 加密
    public static String encode(String data) {
        try {
            if (null == data) {
                return null;
            }
            return new String(Base64.encodeBase64(data.getBytes(charset)), charset);
        } catch (UnsupportedEncodingException e) {
            log.error(String.format("字符串:%s,加密异常", data), e);
        }
        return null;
    }

}
JwtConst.java
public interface JwtConst {
    // 代表这个JWT的接收对象
    String clientId = "098f6bcd4621d373cade4e832627b4f6";
    // 密钥, 经过Base64加密, 可自行替换
    String secret = "MDk4ZjZiY2Q0NjIxZD";
    // JWT的签发主体,存入issuer
    String name = "restapiuser";
    // 过期时间,单位毫秒
    long expires = 60000;
    // 从客户端传递过来的token的名称
    String token = "token";
}
JwtTokenUtil.java
@Slf4j
public class JwtTokenUtil {

    // 解析jwt
    public static Claims parseJWT(String jsonWebToken, String base64Security) {
        try {
            Claims claims = Jwts.parser()
                   .setSigningKey(DatatypeConverter.parseBase64Binary(base64Security))
                   .parseClaimsJws(jsonWebToken).getBody();
            return claims;
        } catch (ExpiredJwtException  eje) {
            log.error("===== Token过期 =====", eje);
            throw new CustomException(ResultCode.PERMISSION_TOKEN_EXPIRED);
        } catch (Exception e){
            log.error("===== token解析异常 =====", e);
            throw new CustomException(ResultCode.PERMISSION_TOKEN_INVALID);
        }
    }

    // 构建jwt
    public static String createJWT(String userId, String username, String role) {
        try {
            // 使用HS256加密算法
            SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

            long nowMillis = System.currentTimeMillis();
            Date now = new Date(nowMillis);

            //生成签名密钥
            byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(JwtConst.secret);
            Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());

            //userId是重要信息,进行加密下
            String encryUserId = Base64Util.encode(userId);

            //添加构成JWT的参数
            JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT")
                    // 可以将基本不重要的对象信息放到claims
                    .claim("role", role)
                    .claim("userId", encryUserId)
                    .setSubject(username)           // 代表这个JWT的主体,即它的所有人
                    .setIssuer(JwtConst.clientId)              // 代表这个JWT的签发主体
                    .setIssuedAt(new Date())       // 是一个时间戳,代表这个JWT的签发时间
                    .setAudience(JwtConst.name)          // 代表这个JWT的接收对象
                    .signWith(signatureAlgorithm, signingKey);
            //添加Token过期时间
            long TTLMillis = JwtConst.expires;
            if (TTLMillis >= 0) {
                long expMillis = nowMillis + TTLMillis;
                Date exp = new Date(expMillis);
                builder.setExpiration(exp)  // 是一个时间戳,代表这个JWT的过期时间;
                        .setNotBefore(now); // 是一个时间戳,代表这个JWT生效的开始时间 
            }
            //生成JWT
            return builder.compact();
        } catch (Exception e) {
            log.error("签名失败", e);
            throw new CustomException(ResultCode.PERMISSION_SIGNATURE_ERROR);
        }
    }

    // 从token中获取用户名
    public static String getUsername(String token){
        return parseJWT(token, JwtConst.secret).getSubject();
    }

    // 从token中获取用户ID
    public static String getUserId(String token){
        String encryUserId = parseJWT(token, JwtConst.secret).get("userId").toString();
        return Base64Util.decode(encryUserId);
    }

    // 是否已过期
    public static boolean isExpiration(String token) {
        return parseJWT(token, JwtConst.secret).getExpiration().before(new Date());
    }

}
第四步:通用类 Result.java
@Data
@ToString
public class Result {
    private Integer code;
    private String msg;
    private T data;


    public Result(String msg) {
        this.msg = msg;
    }

    public Result(ResultCode resultCode) {
        this.code = resultCode.getCode();
        this.msg = resultCode.getMsg();
    }

    public Result(ResultCode resultCode, T data) {
        this(resultCode);
        this.data = data;
    }
    
    public static Result SUCCESS() {
        return new Result(ResultCode.SUCCESS);
    }

    public static  Result SUCCESS(T data) {
        return new Result(ResultCode.SUCCESS, data);
    }

    public static Result FAIL() {
        return new Result(ResultCode.FAIL);
    }

    public static Result FAIL(String msg) {
        return new Result(msg);
    }

}
ResultCode.java
public enum ResultCode {

    /* 成功状态码 */
    SUCCESS(0,"操作成功!"),

    /* 错误状态码 */
    FAIL(-1,"操作失败!"),

    /* 参数错误:10001-19999 */
    PARAM_IS_INVALID(10001, "参数无效"),

    /* 用户错误:20001-29999*/
    USER_NOT_LOGGED_IN(20001, "用户未登录,请先登录"),

    /* 系统错误:40001-49999 */
    SYSTEM_INNER_ERROR(40001, "系统繁忙,请稍后重试"),


    /* 权限错误:70001-79999 */
    PERMISSION_TOKEN_EXPIRED(70004, "token已过期"),
    PERMISSION_TOKEN_INVALID(70006, "无效token"),
    PERMISSION_SIGNATURE_ERROR(70007, "签名失败");

    //操作代码
    int code;
    //提示信息
    String msg;

    ResultCode(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}
CustomException.java
public class CustomException extends RuntimeException {
    //错误代码
    ResultCode resultCode;

    public CustomException(ResultCode resultCode){
        super(resultCode.getMsg());
        this.resultCode = resultCode;
    }

    public CustomException(ResultCode resultCode, Object... args){
        super(resultCode.getMsg());
        String message = MessageFormat.format(resultCode.getMsg(), args);
        resultCode.setMsg(message);
        this.resultCode = resultCode;
    }

    public ResultCode getResultCode(){
        return resultCode;
    }
}
GlobalExceptionHandler.java
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    // 处理自定义异常
    @ExceptionHandler(CustomException.class)
    public Result handleException(CustomException e) {
        // 打印异常信息
        log.error("### 异常信息:{} ###", e.getMessage());
        return new Result(e.getResultCode());
    }

    // 参数错误异常
    @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
    public Result handleException(Exception e) {
        if (e instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException validException = (MethodArgumentNotValidException) e;
            BindingResult result = validException.getBindingResult();
            StringBuffer errorMsg = new StringBuffer();
            if (result.hasErrors()) {
                List errors = result.getAllErrors();
                errors.forEach(p ->{
                    FieldError fieldError = (FieldError) p;
                    errorMsg.append(fieldError.getDefaultMessage()).append(",");
                    log.error("### 请求参数错误:{"+fieldError.getObjectName()+"},field{"+fieldError.getField()+ "},errorMessage{"+fieldError.getDefaultMessage()+"}"); });
            }
        } else if (e instanceof BindException) {
            BindException bindException = (BindException)e;
            if (bindException.hasErrors()) {
                log.error("### 请求参数错误: {}", bindException.getAllErrors());
            }
        }
        return new Result(ResultCode.PARAM_IS_INVALID);
    }

    // 处理所有不可知的异常
    @ExceptionHandler(Exception.class)
    public Result handleOtherException(Exception e){
        //打印异常堆栈信息
        e.printStackTrace();
        // 打印异常信息
        log.error("### 不可知的异常:{} ###", e.getMessage());
        return new Result(ResultCode.SYSTEM_INNER_ERROR);
    }
}
第五步:自定义注解 Logintoken.java
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginToken {
    boolean required() default true;
}
Passtoken.java
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
    boolean required() default true;
}
第六步:自定义拦截器并注册 JwtInterceptor.java
@Slf4j
public class JwtInterceptor implements HandlerInterceptor {
    @Autowired
    UserService userService;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
        // 如果不是映射到方法直接通过
        if (!(object instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();

        //检查是否有passtoken注释,有则跳过认证
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                log.info("当前所访问的资源不需要登录");
                return true;
            }
        }
        //检查有没有需要用户权限的注解
        if (method.isAnnotationPresent(LoginToken.class)) {
            LoginToken loginToken = method.getAnnotation(LoginToken.class);
            if (loginToken.required()) {
                // 从 http 请求头中取出 token
                String authHeaderKey = JwtConst.token;
                String token = request.getHeader(authHeaderKey);
                if (token == null || token.length() == 0) {
                    token = request.getParameter(authHeaderKey);
                }
                if (token == null || token.length() == 0) {
                    log.info("用户未登录,请先登录");
                    throw new CustomException(ResultCode.USER_NOT_LOGGED_IN);
                }
                String userId = JwtTokenUtil.getUserId(token);

                User user = userService.selectByPrimaryKey(Integer.parseInt(userId));
                if (user == null) {
                    log.info("用户不存在,请重新登录");
                    throw new RuntimeException("用户不存在,请重新登录");
                }
                log.info("用户存在,登录成功");
                return true;
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
    }
}
InterceptorConfig.java
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {//跨域请求
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                .allowCredentials(true)
                .maxAge(3600)
                .allowedHeaders("*");
    }
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor())
                .addPathPatterns("/**");    //用于设置拦截器的过滤路径规则
// 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
    }
    @Bean
    public JwtInterceptor authenticationInterceptor() {
        return new JwtInterceptor();
    }
}
第七步:MyBatis操作相关 实体类User.jva
@Getter
@Setter
@ToString
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private static final long serialVersionUID = -8390887042859558153L;
    private Integer id;
    private String username;
    private String password;
}

UserDao.java
@Mapper
public interface UserMapper {
    User selectByPrimaryKey(Integer id);

    List selectByUsername(@Param("username")String username);
}
UserMapper.xml



    
        select
        id, username, `password`
        from db_test.tb_user
        where id = #{id}
    
    
        select
        id, username, `password`
        from db_test.tb_user
        where username=#{username}
    

UserService.java
public interface UserService {
    User selectByPrimaryKey(Integer id);
    List selectByUsername(String username);
}
UserServiceImp.java
@Service
public class UserServiceImpl implements UserService{
    @Resource
    private UserMapper userMapper;
    @Override
    public User selectByPrimaryKey(Integer id) {
        return userMapper.selectByPrimaryKey(id);
    }
    @Override
    public List selectByUsername(String username) {
        return userMapper.selectByUsername(username);
    }
}
第八步:Controller
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    @Autowired
    private UserService userService;

    //登录
    @RequestMapping("/login")
    public Result login(HttpServletResponse response, String username, String password) {
        List userList = userService.selectByUsername(username);
        if (userList == null || userList.size() == 0) {
            log.info("登录失败,用户不存在");
            return Result.FAIL("登录失败,用户不存在");
        }
        String role = "admin";
        for (User user : userList) {
            if (user.getPassword().equals(password)) {
                log.info("登录成功");
                String token = JwtTokenUtil.createJWT(user.getId()+"",username,role);
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("token",token);
                return Result.SUCCESS(jsonObject);
            }
        }
        log.info("登录失败,密码错误");
        return Result.FAIL("登录失败,密码错误");
    }

    @LoginToken
    @RequestMapping("/getUserById")
    public User getUserById(HttpServletResponse response,Integer userId) {
        log.info("访问需要登录才能访问的受保护的资源");
        User user = userService.selectByPrimaryKey(userId);
        return user;
    }

    // 不加注解的话默认不验证,登录接口一般是不验证的。
    // getMessage()加上了登录注解,说明该接口必须登录获取token后,在请求头中加上token并通过验证才可以访问
    @PassToken
    @GetMapping("/getMsg")
    public String getMsg() {
        log.info("访问不需要登录就能访问的资源");
        return "不需要通过验证";
    }
}
前端代码

创建项目,然后将jquery-2.2.4.min.js库拷贝到项目的根路径下的js目录中。项目结构如下图所示: 在这里插入图片描述

login.html



    
    登录页面
    


    
         
        
        
    
    

    
        $("#login").click(function () {
            $.ajax({
                url: "http://localhost:80/jd/user/login",
                type: "POST",
                data: {
                    username: $("#username").val(),
                    password: $("#password").val()
                },
                dataType: "json",
                success: function (res) {
                    localStorage.setItem("token",res.data.token);
                    window.location.href="user.html";
                },
                error:function () {
                    $("#show").html("error");
                }
            });
        });
    


user.html



    
    用户信息页面
    


    请求公共信息
    

    
        $.ajax({
            url: "http://localhost:80/jd/user/getUserById",
            type: "POST",
            data: {
                userId: 1001,
                token:localStorage.getItem("token")
            },
            dataType: "json",
            success:function (res) {
                $("#emp").text(JSON.stringify(res));
            },
            error:function () {
                console.log("error");
            }
        });
    


测试

在login.html页面中请求后台,然后在打开的user.html页面中请求公共信息,在控制台中看到如下信息: 在这里插入图片描述

关注
打赏
1665409997
查看更多评论
立即登录/注册

微信扫码登录

0.0510s