Session和JWT在项目中的使用
本博文将就本人在javaWeb和几个项目中所用到的session和JWT进行用户身份验证的方法,对这两种客户端和服务器交互的方式进行介绍和代码展示。
文章目录
Session在项目中的使用
一、使用Session进行用户登录验证
代码只做示例,仅供参考
还要有数据库连接和yml文件配置等。
这篇博文只做简单的介绍,还要就是自己对登录认证这块知识的复习巩固
1、首先用户类【Mapper就不再介绍了】
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value="User对象", description="用户表")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "用户id")
@TableId
private Long userId;
@ApiModelProperty(value = "用户名称")
private String username;
@ApiModelProperty(value = "电话")
private String phone;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "如果用户传入头像,则用用户的;如果没传,则默认")
private String avatar;
@ApiModelProperty(value = "用户介绍")
private String description;
@ApiModelProperty(value = "普通用户是0,管理员是1")
private Integer roles;
@ApiModelProperty(value = "创建时间")
private LocalDateTime createTime;
@ApiModelProperty(value = "修改时间")
private LocalDateTime updateTime;
@ApiModelProperty(value = "逻辑删除字段:0 未删除,1 删除")
private Integer isDelete;
}
2、创建登录和退出接口
/**
* 登录
*
* 这里需要说明的是:
* UserQueryResp 是特殊返回类,包含指定返回内容
* UserLoginReq 是特殊请求类,包含指定请求内容
*/
UserQueryResp login(UserLoginReq req, HttpSession session);
/**
* 注销
*
* @param session 会话
*/
void logOut(HttpSession session);
3、在登录接口的实现类里编写代码
今天主题是Session,那么在代码中
session.setAttribute(SESSION_KEYWORDS,userSession);
就是设置当前用户的Session,当用户登录,通过拦截器【下面编写】后,服务器就会产生session,返回SeesionID给浏览器。
/**
* <p>
* 用户表 服务实现类
* </p>
*
* @author yzh
* @since 2022-08-13
*/
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Resource
private UserMapper userMapper;
@Resource
private CommonService commonService;
/**
* 登录
* yzh
* 代码中使用了
* 1、MyBatis-Plus的条件查询
* 2、自定义异常 BusinessException
* 3、枚举类-异常值 BusinessCode
* 4、Md5 加密处理 DigestUtils.md5DigestAsHex()
* 5、自定义拷贝类 CopyUtil.copy【类似于BeanUtils的copyproperties方法】
*/
@Override
public UserQueryResp login(UserLoginReq req, HttpSession session) {
//1、 根据phone查询数据库是否有此用户
User userDb = this.getOne(Wrappers.lambdaQuery(User.class).eq(User::getPhone, req.getPhone()));
// 如果数据库中无此用户
if (ObjectUtils.isEmpty(userDb)){
// 前端显示错误原因
throw new BusinessException(BusinessCode.LOGIN_ERROR,BusinessCode.LOGIN_ERROR.getMessage());
}
// 2、对比判断输入密码是否正确
String reqPwd = DigestUtils.md5DigestAsHex((req.getPassword() + SALT).getBytes());
if (!reqPwd.equals(userDb.getPassword())){
throw new BusinessException(BusinessCode.LOGIN_ERROR,BusinessCode.LOGIN_ERROR.getMessage());
}
// 3、将当前用户信息存入session
User userSession = new User();
userSession.setUserId(userDb.getUserId());// 拦截器要用到
session.setAttribute(SESSION_KEYWORDS,userSession);
return CopyUtil.copy(userDb,UserQueryResp.class);
}
/**
* 退出登录
*
* 退出登录,直接调用HttpSession的removeAttribute()方法
*/
@Override
public void logOut(HttpSession session) {
session.removeAttribute(SESSION_KEYWORDS);
}
}
4、在登录控制器Controller中编写代码
Controller代码中就是对实现类的逻辑操作进行调用,为前端调用提供接口
/**
*yzh
*/
@RestController
@RequestMapping("/user")
@Api(tags = "用户接口")
public class UserController {
@Resource
private UserService userService;
/**
* @Valid校验
* @RequestBody 适应JSON格式
* CommonResponse 自定义的通用返回类
*/
@PostMapping("/login")
@ApiOperation("用户登录")
public CommonResponse<UserQueryResp> login(@RequestBody @Valid UserLoginReq req, HttpSession session){
return CommonResponse.success(userService.login(req,session),"用户登录成功!");
}
@PostMapping("/logout")
@ApiOperation("退出登录")
public CommonResponse<String> logOut(HttpSession session){
userService.logOut(session);
return CommonResponse.success("用户退出登录成功!");
}
}
5、拦截器中编写代码
HttpSession session = request.getSession();
User sessionUser = (User) session.getAttribute(SESSION_KEYWORDS);
这段代码就是通过Session来获取当前用户,确定唯一用户
/**
* 登录拦截器
*
* @author yzh
* @since 2022/8/16
*/
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Resource
private UserMapper userMapper;
/**
* 前处理 进行拦截处理
*
* HttpSession session = request.getSession();
User sessionUser = (User) session.getAttribute(SESSION_KEYWORDS);
这段代码就是通过Session来获取当前用户,确定唯一用户
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("-------start interceptor------");
// 通过session得到当前用户,进行操作
HttpSession session = request.getSession();
User sessionUser = (User) session.getAttribute(SESSION_KEYWORDS);
if (ObjectUtils.isEmpty(sessionUser)){
throw new BusinessException(BusinessCode.USER_MESSAGE_ERROR,"此用户未登录!");
}
// 将处理程序封装为HandlerMethod
HandlerMethod handlerMethod = (HandlerMethod) handler;
// handlerMethod.hasMethodAnnotation(AdminRole.class)返回boolean值
// 当返回 true 时,进行鉴别操作
/*if (sessionUser.getIsDelete()==1){
throw new BusinessException(BusinessCode.USER_MESSAGE_ERROR,"此用户已被注销,请重试!");
}*/
if (handlerMethod.hasMethodAnnotation(AdminRole.class)){
// 通过session传入的Id来匹配当前用户信息,得到当前用户对象
User user = userMapper.selectById(sessionUser.getUserId());
if (ObjectUtils.isEmpty(user)){
throw new BusinessException(BusinessCode.USER_MESSAGE_ERROR,"此用户不存在!");
}
if (!user.getRoles().equals(ADMIN_ROLE)){
throw new BusinessException(BusinessCode.USER_MESSAGE_ERROR,"此用户无权限!");
}
}
return true;
}
}
Session在其他模块也都有使用,但是就只展示这么多了
像用户更新、购买物品等等,都需要通过Session验证是否为当前用户
6、上文的一些类
CommonResponse
/**
* 公共返回类
*
* @author yzh
* @date 2022/08/15
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResponse<T> implements Serializable {
private static final long serialVersionUID = 112345679815613L;
private boolean success;
private T data;
private String message;
/**
* 成功
*
* @return {@link CommonResponse}<{@link T}>
*/
public static <T> CommonResponse<T> success(T data, String message) {
return new CommonResponse<>(true, data, message);
}
/**
* 成功
*
* @return {@link CommonResponse}<{@link T}>
*/
public static <T> CommonResponse<T> success(String message) {
return new CommonResponse<>(true, null, message);
}
/**
* 成功
*
* @return {@link CommonResponse}<{@link T}>
*/
public static <T> CommonResponse<T> success() {
return new CommonResponse<>(true, null, "");
}
/**
* 错误
*
* @param code 代码
* @param message 消息
* @return {@link CommonResponse}<{@link Object}>
*/
public static CommonResponse<Object> error(BusinessCode code, String message) {
return new CommonResponse<>(false, code.getMessage(), message);
}
}
BusinessException
/**
* 业务异常
*
* @author yzh
* @date 2022/08/15
*/
public class BusinessException extends RuntimeException {
private final BusinessCode businessCode;
private final String message;
public BusinessException(BusinessCode businessCode, String message) {
this.businessCode = businessCode;
this.message = message;
}
@Override
public String getMessage() {
return message;
}
public BusinessCode getBusinessCode() {
return businessCode;
}
@Override
public Throwable fillInStackTrace() {
return this;
}
}
BusinessCode
/**
* 业务代码
*
* @author yzh
* @date 2022/08/15
*/
public enum BusinessCode {
PARAM_ERROR("参数错误"),
LOGIN_ERROR("账号或密码错误"),
USER_MESSAGE_ERROR("此用户信息错误"),
AUTH_ERROR("权限错误"),
FILE_ERROR("文件异常"),
ASSESS_ERROR("评价异常"),
BUY_ERROR("购买异常"),
PRODUCT_ERROR("商品异常")
;
private final String message;
BusinessCode(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
CopyUtil
package com.yzh.utils;
import org.springframework.beans.BeanUtils;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
/**
* 复制工具类
*
* @author yzh
* @since 2022/08/05
*/
public class CopyUtil {
/**
* 单体复制
*
* @param source 源
* @param clazz clazz
* @return {@link T}
*/
public static <T> T copy(Object source, Class<T> clazz) {
if (source == null) {
return null;
}
T obj;
try {
obj = clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
e.printStackTrace();
return null;
}
BeanUtils.copyProperties(source, obj);
return obj;
}
/**
* 列表复制
*
* @param source 源
* @param clazz clazz
* @return {@link List}<{@link T}>
*/
public static <T> List<T> copyList(List<?> source, Class<T> clazz) {
List<T> target = new ArrayList<>();
if (!CollectionUtils.isEmpty(source)) {
for (Object c : source) {
T obj = copy(c, clazz);
target.add(obj);
}
}
return target;
}
}
JWT在项目中的使用
一、单纯使用JWT
1、新建实体类
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
private Integer id;
private String name;
private String password;
}
2、dao层
@Mapper
public interface UserDao {
/**
* 登录方法
* @param user
* @return
*/
User login(User user);
}
3、UserDao.xml文件
注意: 路径要正确
在application.properties中设置的路径也要正确
mybatis.type-aliases-package=com.lut.entity
mybatis.mapper-locations=classpath:mapper/*.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lut.dao.UserDao">
<select id="login" parameterType="User" resultType="User">
select id,name,password from user where name=#{name} and password=#{password}
</select>
</mapper>
4、service层
@Service
public interface UserService {
/**
* 登录方法
* @param user
* @return
*/
User login(User user);
}
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public User login(User user) {
//接受用户查询数据库
User userDB = userDao.login(user);
//查询到这个用户就返回,没有则抛出错误
if (userDB != null) {
return userDB;
}else{
throw new RuntimeException("登录失败!");
}
}
}
5、controller层
@RestController
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/test")
public String test(User user) {
return "ok";
}
@GetMapping("/user/login")
public Map<String, Object> login(User user) {
log.info("用户名:[{}]", user.getName());
log.info("密码:[{}]", user.getPassword());
HashMap<String, Object> map = new HashMap<>();
try {
//获取user对象
User userDB = userService.login(user);
//将user对象添加到map
Map<String, String> payload = new HashMap<>();
payload.put("id", userDB.getId().toString());
payload.put("name", userDB.getName());
//生成令牌
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;
}
@PostMapping("/user/test")
public Map<String, Object> test(String token) {
Map<String, Object> map = new HashMap<>();
log.info("当前token为:[{}]", token);
try {
DecodedJWT verify = JWTUtils.verify(token);
map.put("state", true);
map.put("msg", "请求成功");
return map;
} catch (SignatureVerificationException e) {
map.put("msg", "签名不一致异常");
e.printStackTrace();
} catch (TokenExpiredException e) {
map.put("msg", "令牌过期异常");
e.printStackTrace();
} catch (AlgorithmMismatchException e) {
map.put("msg", "算法不匹配异常");
e.printStackTrace();
} catch (Exception e) {
map.put("msg", "token无效");
e.printStackTrace();
}
map.put("state", false);
return map;
}
}
6、测试
使用ApiFox或者Postman都可以
7、拦截器
确定哪些内容要拦截
代码只是演示
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//只做示例
registry.addInterceptor(new JWTInterceptor())
.addPathPatterns("/user/test") //其他接口保护
.excludePathPatterns("/user/login");//所有用户接口放行
}
}
在处理路径之前拦截
public class Login Interceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Map<String,Object> map=new HashMap<>();
//获取请求头中的令牌
String token=request.getHeader("token");
try {
//验证令牌
JWTUtils.decodedJWT(token);
//放行请求
return true;
} catch (SignatureVerificationException e) {
map.put("msg", "签名不一致异常");
e.printStackTrace();
} catch (TokenExpiredException e) {
map.put("msg", "令牌过期异常");
e.printStackTrace();
} catch (AlgorithmMismatchException e) {
map.put("msg", "算法不匹配异常");
e.printStackTrace();
} catch (Exception e) {
map.put("msg", "token无效");
e.printStackTrace();
}
map.put("state",false);
//将map 转为 json
String json=new ObjectMapper().writeValueAsString(map);
response.setContentType("appliaction/json;charset=UTF-8");
return false;
}
}
测试结果
最好是将 令牌 封装在 header 中
二、结合SpringSecurity用户验证和权限管理
暂无