基于传统session的认证
http协议本身是一种无状态的协议,如果用户向服务器提供用户名和密码进行认证,那么下一次请求时,用户还需要再进行用户认证。根据http协议,服务器无法确定是哪个用户发出的请求,所以只能在服务器端存储一份用户认证的信息。这份认证信息会在响应时传递到客户端,保存为cookie,以便下次请求时服务器能识别来自哪个用户,无需再进行用户认证。
传统session认证
传统session的弊端
- session通常保存在服务端内存中,随着认证用户的增多,服务端的开销会明显增大。
- session保存在某一台服务器的内存中,这意味着用户下次请求还必须是这台服务器,这在分布式应用上限制了负载均衡器的能力。
- 客户端需要携带cookie,如果cookie被截取,很容易收到跨站请求伪造共计。
JWT是什么?
JWT(Json Web Token)通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输。在数据传输前还能完成数据加密、签名等处理。
JWT能做什么?
授权:JWT的最常见方案。一旦用户登录,后续的请求将包括JWT,允许用户访问令牌允许的路由、服务器、资源。
JWT的优势
- 简洁,传输速度快。
- 自包含:负载中包含用户所需的信息,避免多次查询数据库。
- 令牌是以JSON加密保存在客户端,所以JWT是跨语言的,原则上任何Web形式都支持。
- 无需在服务端保存用户信息,适用于分布式微服务。
JWT代码演示
- Pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.itcast</groupId> <artifactId>Jwt</artifactId> <version>1.0-SNAPSHOT</version> <name>Jwt</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <parent> <artifactId>spring-boot-starter-parent</artifactId> <groupId>org.springframework.boot</groupId> <version>2.2.0.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.4</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.3</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.2.0.RELEASE</version> </plugin> </plugins> </build> </project>
- resource/application.yml
server: port: 8090 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/exercise?serverTimezone=UTC username: root password: Wy19971124 mybatis: mapper-locations: classpath:/mapper/*.xml logging: level: cn: itcast: dao: debug
- resource/mapper/UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.itcast.dao.UserDao">
<select id="login" resultType="cn.itcast.domain.User">
select * from user where name=#{name} and password=#{password}
</select>
</mapper>
- src/main/java/cn.itcast/application.java
package cn.itcast; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("cn.itcast.dao") public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
- src/main/java/cn.itcast/domain/User.java
package cn.itcast.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private String password;
}
- src/main/java/cn.itcast/dao/UserDao.interface
package cn.itcast.dao;
import cn.itcast.domain.User;
public interface UserDao {
User login(User user);
}
- src/main/java/cn.itcast/service/UserService.interface
package cn.itcast.service;
import cn.itcast.domain.User;
public interface UserService {
User login(User user);
}
- src/main/java/cn.itcast/service/impl/UserServiceImpl.java
package cn.itcast.service.impl;
import cn.itcast.dao.UserDao;
import cn.itcast.domain.User;
import cn.itcast.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public User login(User user) {
User loginUser = userDao.login(user);
if(loginUser != null) {
return loginUser;
}
else {
throw new RuntimeException("登录失败");
}
}
}
- src/main/java/cn.itcast/interceptors/JwtInterceptor.java(编写拦截器)
package cn.itcast.interceptors;
import cn.itcast.utils.JwtUtils;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
public class JwtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token"); // 获取请求头中的令牌
Map<String, Object> map = new HashMap<>();
try {
DecodedJWT tokenInfo = JwtUtils.getTokenInfo(token); // 获取令牌
return true;
}
catch (SignatureVerificationException e) {
e.printStackTrace(); // 签名异常
map.put("msg", "无效签名");
}
catch (TokenExpiredException e) {
e.printStackTrace(); // 过期异常
map.put("msg", "令牌过期");
}
catch (AlgorithmMismatchException e) {
e.printStackTrace(); // 算法异常
map.put("msg", "算法异常");
}
catch (Exception e) {
e.printStackTrace(); // 总异常
map.put("msg", "令牌无效");
}
map.put("state", false);
// 将map转为json
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
return false;
}
}
- src/main/java/cn.itcast/config/InterceptorConfig.java(配置拦截器)
package cn.itcast.config;
import cn.itcast.interceptors.JwtInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JwtInterceptor())
.addPathPatterns("/UserController/verify") // 拦截/UserController/verify
.excludePathPatterns("/UserController/login"); //放行/UserController/login
}
}
- src/main/java/cn.itcast/controller/UserController.java
package cn.itcast.controller;
import cn.itcast.domain.User;
import cn.itcast.service.UserService;
import cn.itcast.utils.JwtUtils;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@Controller
@RequestMapping("/UserController")
@ResponseBody
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/login")
public Map<String, Object> login(User user) {
log.info("用户名:[{}]", user.getName());
log.info("密码:[{}]", user.getPassword());
Map<String, Object> map = new HashMap<>();
try {
User loginUser = userService.login(user);
Map<String, String> payload = new HashMap<>();
payload.put("id", loginUser.getId().toString());
payload.put("name", loginUser.getName());
String token = JwtUtils.getToken(payload); // 生成JWT令牌
map.put("state", true);
map.put("msg", "登录成功");
map.put("token", token);
}
catch (Exception e) {
map.put("state", false);
map.put("msg", e.getMessage());
}
return map;
}
@PostMapping("/verify")
public Map<String, Object> verify(HttpServletRequest request) {
Map<String, Object> map = new HashMap<>();
String token = request.getHeader("token");
DecodedJWT tokenInfo = JwtUtils.getTokenInfo(token);
log.info("用户id:[{}]", tokenInfo.getClaim("id").asString());
log.info("用户名:[{}]", tokenInfo.getClaim("name").asString());
map.put("state", true);
map.put("msg", "请求成功");
return map;
}
}
编写和配置拦截器是难点
实验
首次登录访问:
请求头中携带令牌认证:
转载自:https://zhuanlan.zhihu.com/p/352351265