Session和JWT在项目中的使用

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

扫描二维码关注公众号,回复: 15824394 查看本文章
/**
 * 业务代码
 *
 * @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用户验证和权限管理

暂无

猜你喜欢

转载自blog.csdn.net/AN_NI_112/article/details/131502664
今日推荐