前言
Session认证
我们都知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道哪个用户发出的请求,所以为了让我们的应用程序能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登陆的信息,这份登录信息会在响应式传递给浏览器,告诉其保存为cookie,以便下次请求是发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
缺点:
扩展性:
Session的扩展性比较差,因为需要做多太机器数据共享
网络安全:
CSRF攻击:因为基于cookie来进行用户识别, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
跨平台:
在移动应用上 session 和 cookie 很难行通,你无法与移动终端共享服务器创建的 session 和 cookie。
JWT使用
首先加入maven依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
后台生成token,我将这个封装成了三个方法,包括生成token,验证token,获取token中的信息,当然,第三个和第二个差不多可以只写第二个
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Map;
/**
* jwt封装
*
* */
public class JwtUtils {
//创建一个常量作为签名
private static final String SING = "ASID#2@$OSJ";
//封装一个方法,参数为Map类型,传给payload
public static String getToken(Map<String,String> map){
Calendar instance = Calendar.getInstance(); //创建一个日期的对象
instance.add(Calendar.DATE,7); //设置过期时间为7天
//创建jwt builder
JWTCreator.Builder builder = JWT.create();
//将map中的k,v传给payload
map.forEach((k,v)->{
builder.withClaim(k,v);
});
//withExpiresAt() 设置一个过期的时间,最后选择使用的算法进行加密,将签证丢进去
String token = builder.withExpiresAt(instance.getTime()).sign(Algorithm.HMAC256(SING));
//最后生成一个token,将token返回
return token;
}
//验证token
public static DecodedJWT verify(String token){
return JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
}
//获取token中的信息
public static DecodedJWT getTokenInformation(String token){
DecodedJWT verify = JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
return verify;
}
}
JWT在Springboot中的使用
首先导入依赖,只是简单的用户验证最主要的还是理解token的使用
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
创建一个数据库
对应的实体类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String username;
private String password;
}
写一个简单的登录Mapper接口
import com.tjs.pojo.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper {
User login (User user);
}
再写一个对应的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="com.tjs.mapper.UserMapper">
<select id="login" parameterType="com.tjs.pojo.User" resultType="com.tjs.pojo.User">
select * from user where username = #{username} and password = #{password}
</select>
</mapper>
写一个service接口
import com.tjs.pojo.User;
import org.springframework.stereotype.Service;
@Service
public interface UserService {
User login (User user);
}
写一个类来实现service
import com.tjs.mapper.UserMapper;
import com.tjs.pojo.User;
import com.tjs.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
@Transactional(propagation = Propagation.SUPPORTS)
public User login(User user) {
User logins = userMapper.login(user);
if (logins != null){
return logins;
}
throw new RuntimeException("登录失败");
}
}
写一个Controller
import com.auth0.jwt.interfaces.DecodedJWT;
import com.tjs.pojo.User;
import com.tjs.service.UserService;
import com.tjs.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@RestController
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/user/login")
public Map<String,Object> login(User user){
log.info("用户名:[{}]",user.getUsername());
log.info("密码:[{}]",user.getPassword());
Map<String,Object> map = new HashMap();
try {
User login = userService.login(user);
Map<String,String> payload = new HashMap<>();
payload.put("userid","1");
payload.put("username",user.getUsername());
String token = JwtUtils.getToken(payload);
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;
}
//在放问接口之前会进入拦截器(InterceptorConfig),将token验证封装到拦截器中可以简化接口代码冗余问题
@PostMapping("/test/token")
public Map<String,Object> verifyToken(HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
/**
* 在处理业务逻辑的时候需要用到userId或者用户名,可以使用HttpServletRequest 下的request.getHeader的
* 方法来获取token
* 然后使用JWT的工具包中verify的方法进行验证
* 验证之后的对象可以调用getClaim方法获取生成token时存入的用户信息
*
* */
String token = request.getHeader("token");
DecodedJWT verify = JwtUtils.verify(token);
String username = verify.getClaim("username").asString();
log.info("username:[{}]",username);
map.put("state",true);
map.put("msg","认证成功");
return map;
}
}
我将登陆时生成token,访问数据时验证token写到了拦截器中,这样可以大大简化接口方法里的代码冗余问题,当然一些分布式项目可以将验证写道网关里,而一些小的微服务可以直接封装到拦截器中。
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.tjs.utils.JwtUtils;
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 {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String token = request.getHeader("token");
Map<String,Object> map = new HashMap<>();
try {
JwtUtils.verify(token);
map.put("state",true);
map.put("msg","认证成功");
return true;
}catch (AlgorithmMismatchException e){
map.put("msg","token算法不一致");
}catch (TokenExpiredException e){
map.put("msg","token过期");
}catch (SignatureVerificationException e){
map.put("msg","签名无效");
}catch (Exception e){
map.put("msg","token无效");
}
map.put("state",false);
/**这里我使用的是fastjson
* 也可以直接使用jackson
* String json = new ObjectMapper().writeValueAsString(map);
*
* */
JSONObject json = new JSONObject(map); //将map转换成json
response.setContentType("application/json;charset=UTF-8"); //指定json格式
response.getWriter().println(json); //将json写入
return false;
}
}
然后可以创建一个config包,创建一个类实现WebMavConfigurer
重写addInterceptors(InterceptorRegistry registry)(添加一个拦截器)
使用@Configuration注解表明这是一个配置类
import com.tjs.interceptor.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 {
//写一个拦截器,将token验证封装到拦截器中
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JwtInterceptor())
.excludePathPatterns("/user/**") //放行接口地址,用户请求都放行
.addPathPatterns("/test/**"); //拦截接口地址,其他接口受保护
}
}
在application.yml中写入配置
server:
port: 端口
spring:
datasource:
username: 你的数据库用户名
password: 你的数据库密码
url: jdbc:mysql://localhost:3306/jwt
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mybatis/mapper/*.xml
type-aliases-package: com.tjs.pojo
开始启动
启动之后访问对应的接口然后会返回一个token
当访问数据接口的时候需要将token放到header中然后请求,这样就可以实现使用JWT单点登录了,这里的token我在JwtUtils工具包中设置的7天,也可以根据具体业务需求设置时长。
结语
今天的分享就到这儿了,希望各位小伙伴可以给点个赞哦,下次再见!