SpringBoot之登录注册接口,MD5加密,JWT登录校验

数据准备:

  • sql建表语句
CREATE TABLE `user` (
 `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
 `name` varchar(128) DEFAULT NULL COMMENT '昵称',
 `pwd` varchar(124) DEFAULT NULL COMMENT '密码',
 `head_img` varchar(524) DEFAULT NULL COMMENT '头像',
 `phone` varchar(64) DEFAULT '' COMMENT '⼿机号',
 `create_time` datetime DEFAULT NULL COMMENT '创建时间',
 PRIMARY KEY (`id`),
 UNIQUE KEY `phone` (`phone`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
  • 实体类
/**
 * `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
 *   `name` varchar(128) DEFAULT NULL COMMENT '昵称',
 *   `pwd` varchar(124) DEFAULT NULL COMMENT '密码',
 *   `head_img` varchar(524) DEFAULT NULL COMMENT '头像',
 *   `phone` varchar(64) DEFAULT '' COMMENT '手机号',
 *   `create_time` datetime DEFAULT NULL COMMENT '创建时间',
 */
public class User {

    private Integer id;

    private String name;

    @JsonIgnore
    private String pwd;

    @JsonProperty("head_img")
    private String headImg;

    private String phone;

    @JsonProperty("create_time")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;


    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                ", headImg='" + headImg + '\'' +
                ", phone='" + phone + '\'' +
                ", createTime=" + createTime +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    public String getHeadImg() {
        return headImg;
    }

    public void setHeadImg(String headImg) {
        this.headImg = headImg;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}

规范controller层返回数据:

public class JsonData {

    /**
     * 状态码 0表示成功过,1表示处理中,-1 表示失败
     */
    private Integer code;

    /**
     * 业务数据
     */
    private Object data;

    /**
     * 信息表示
     */
    private String msg;

    public  JsonData(){}

    public  JsonData(Integer code, Object data, String msg){
        this.code = code;
        this.data = data;
        this.msg = msg;
    }


    /**
     * 成功,不用返回数据
     * @return
     */
    public static JsonData buildSuccess(){
        return new JsonData(0,null,null);
    }

    /**
     * 成功,返回数据
     * @param data
     * @return
     */
    public static JsonData buildSuccess(Object data){
        return new JsonData(0,data,null);
    }


    /**
     * 失败,固定状态码
     * @param msg
     * @return
     */
    public static JsonData buildError(String  msg){
        return new JsonData(-1 ,null,msg);
    }


    /**
     * 失败,自定义错误码和信息
     * @param code
     * @param msg
     * @return
     */
    public static JsonData buildError(Integer code , String  msg){
        return new JsonData(code ,null,msg);
    }


    public Integer getCode() {
        return code;
    }

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

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

MD5加密

MD5加密⼯具类封装(不⽤死记,不常用的⽅法,直接拷⻉使⽤就⾏,基本是写⼀次,其他项⽬直接拷⻉)

/**
 * 工具类
 */
public class CommonUtils {


    /**
     * MD5加密工具类
     * @param data
     * @return
     */
    public static String MD5(String data)  {
        try {
            java.security.MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] array = md.digest(data.getBytes("UTF-8"));
            StringBuilder sb = new StringBuilder();
            for (byte item : array) {
                sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
            }

            return sb.toString().toUpperCase();
        } catch (Exception exception) {
        }
        return null;

    }

}

用户注册功能开发

  • Controller层
@RestController
@RequestMapping("api/v1/pri/user")
public class UserController {


    @Autowired
    private UserService userService;

    /**
     * 注册接口
     *
     * @param userInfo
     * @return
     */
    @PostMapping("register")
    public JsonData register(@RequestBody Map<String, String> userInfo) {

        int rows = userService.save(userInfo);

        return rows == 1 ? JsonData.buildSuccess() : JsonData.buildError("注册失败,请重试");

    }

}
  • Service层(对密码进行MD5加密入库)
public interface UserService {

    /**
     * 新增用户
     * @param userInfo
     * @return
     */
    int save(Map<String, String> userInfo);

}
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;


    @Override
    public int save(Map<String, String> userInfo) {

        User user = parseToUser(userInfo);
        if( user != null){
           return userMapper.save(user);
        }else {
            return -1;
        }

    }
    
    private User parseToUser(Map<String,String> userInfo) {

        if(userInfo.containsKey("phone") && userInfo.containsKey("pwd") && userInfo.containsKey("name")){
            User user = new User();
            user.setName(userInfo.get("name"));
            user.setHeadImg("");
            user.setCreateTime(new Date());
            user.setPhone(userInfo.get("phone"));
            String pwd = userInfo.get("pwd");
            //MD5加密
            user.setPwd(CommonUtils.MD5(pwd));

            return user;
        }else {
            return null;
        }

    }
    
}

  • Dao层:
public interface UserMapper {

    int save(User user);
}
<?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.study.mapper.UserMapper">

    <insert id="save" parameterType="User">

        INSERT  INTO user (name, pwd, head_img, phone , create_time)
        values (#{name,jdbcType=VARCHAR}, #{pwd,jdbcType=VARCHAR}, #{headImg,jdbcType=VARCHAR},
        #{phone,jdbcType=VARCHAR},#{createTime,jdbcType=TIMESTAMP})

    </insert>
  
</mapper>
  • 测试

在这里插入图片描述

在这里插入图片描述

JWT登录校验

**简介:讲解单机和分布式应⽤下登录校验,session共享,分布式缓存使⽤ **

  • 单机tomcat应⽤登录检验
    • sesssion保存在浏览器和应⽤服务器会话之间
    • ⽤户登录成功,服务端会保存⼀个session,当然客户端有⼀个sessionId
    • 客户端会把sessionId保存在cookie中,每次请求都会携带这个sessionId
  • 分布式应⽤中session共享
    • 真实的应⽤不可能单节点部署,所以就有个多节点登录session共享的问题需要解决
    • tomcat⽀持session共享,但是有⼴播⻛暴;⽤户量⼤的时候,占⽤资源就严重,不推荐
    • 使⽤redis存储token:
    • 服务端使⽤UUID⽣成随机64位或者128位token,放⼊redis中,然后返回给客户端并存 储在cookie中
    • ⽤户每次访问都携带此token,服务端去redis中校验是否有此⽤户即可

![image.png](https://img-blog.csdnimg.cn/img_convert/7736ffc92da88a5d938fb23417f1eae6.png#clientId=u102003f8-f1bc-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=792&id=u423da16e&margin=[object Object]&name=image.png&originHeight=1188&originWidth=1295&originalType=binary&ratio=1&rotation=0&showTitle=false&size=142218&status=done&style=none&taskId=u51575bde-7da8-408c-be1a-2b4e1caa515&title=&width=863.3333333333334)

**简介:分布式应⽤的登录检验解决⽅案 JWT讲解 json wen token **
什么是JWT

  • JWT 是⼀个开放标准,它定义了⼀种⽤于简洁,⾃包含的⽤于通信双⽅之间以 JSON 对象的形式安全传递信息的⽅法。 可以使⽤ HMAC 算法或者是 RSA 的公钥密钥对进⾏签名
  • 简单来说: 就是通过⼀定规范来⽣成token,然后可以通过解密算法逆向解密token,这样就可以获取⽤户信息
  • 优点
    • ⽣产的token可以包含基本信息,⽐如id、⽤户昵称、头像等信息,避免再次查库
    • 存储在客户端,不占⽤服务端的内存资源
  • 缺点
    • token是经过base64编码,所以可以解码,因此token加密前的对象不应该包含敏感信息,如⽤户权限,密码等
    • 如果没有服务端存储,则不能做登录失效处理,除⾮服务端改秘钥
{
 id:888,
 name:'⼩D',
 expire:10000
 }
 
 funtion 加密(object, appsecret){
     xxxx
     return base64( token);
 }

 function 解密(token ,appsecret){
     xxxx
     //成功返回true,失败返回false
 }
  • JWT格式组成 头部、负载、签名
    • header+payload+signature
      • 头部:主要是描述签名算法
      • 负载:主要描述是加密对象的信息,如⽤户的id等,也可以加些规范⾥⾯的东⻄,如iss签发者,exp 过期时间,sub ⾯向的⽤户
      • 签名:主要是把前⾯两部分进⾏加密,防⽌别⼈拿到token进⾏base解密后篡改token
  • 关于jwt客户端存储
    • 可以存储在cookie,localstorage和sessionStorage⾥⾯

导入JWT依赖

<!-- JWT相关 -->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt</artifactId>
			<version>0.7.0</version>
		</dependency>

用户登录接口整合JWT登录校验:

  • LoginRequest
/**
 * 登录 request
 */
public class LoginRequest {

    private String phone;

    private String pwd;

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
}
  • controller
@RestController
@RequestMapping("api/v1/pri/user")
public class UserController {


    @Autowired
    private UserService userService;

    /**
     * 注册接口
     *
     * @param userInfo
     * @return
     */
    @PostMapping("register")
    public JsonData register(@RequestBody Map<String, String> userInfo) {

        int rows = userService.save(userInfo);

        return rows == 1 ? JsonData.buildSuccess() : JsonData.buildError("注册失败,请重试");

    }


    /**
     * 登录接口
     *
     * @param loginRequest
     * @return
     */
    @PostMapping("login")
    public JsonData login(@RequestBody LoginRequest loginRequest) {

        String token = userService.findByPhoneAndPwd(loginRequest.getPhone(), loginRequest.getPwd());

        return token == null ? JsonData.buildError("登录失败,账号密码错误") : JsonData.buildSuccess(token);

    }

}
  • service
public interface UserService {

    /**
     * 新增用户
     *
     * @param userInfo
     * @return
     */
    int save(Map<String, String> userInfo);


    String findByPhoneAndPwd(String phone, String pwd);

}
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;


    @Override
    public int save(Map<String, String> userInfo) {

        User user = parseToUser(userInfo);
        if (user != null) {
            return userMapper.save(user);
        } else {
            return -1;
        }

    }


    @Override
    public String findByPhoneAndPwd(String phone, String pwd) {

        User user = userMapper.findByPhoneAndPwd(phone, CommonUtils.MD5(pwd));

        if (user == null) {
            return null;

        } else {
            String token = JWTUtils.geneJsonWebToken(user);
            return token;
        }

    }

}
  • JWT

Jwt工具类
注意点:
1、生成的token, 是可以通过base64进行解密出明文信息
2、base64进行解密出明文信息,修改再进行编码,则会解密失败
3、无法作废已颁布的token,除非改秘钥

认证流程
a.首先,前端通过web表单将自己的用户民和密码发送到后端的接口,这一过程一般是一个HTTP,POST请求。建议的方式是通过SSL加密的方式传输(https协议),从而避免敏感信息被嗅探。
b.后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(token),形成的JWT就是一个形同111.zzz.xxx的字符串, token,head ,payload, singurater
c.后端将JWT字符串 作为登录成功的返回结果返回给前端,前端可以返回的结果保存在localStorage 或sessionStorage上,退出登录时前端删除保存的JWT即可。
d.前端在每次请求时将JWT放入HTTP Header中的Authoriztion 位。(放在Header中的原因是解决XSS和XSRF问题) HEADER.
e.后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)
f。验证通过后,后端使用JWT包含的用户信息进行其他逻辑操作,返回相应结果。

JWT优势

  - 可以通过URL,POST参数或者在HTTP,header发送 ,因为数据量小,传输速度也很快。
  - 自包含(self-contained): 负载中包含了所有用户所需要的信息,避免了多次查询数据库。
  - 因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
  - 不需要在服务端保存会话信息,特别适用于分布式微服务。



public class JWTUtils {


    /**
     * 过期时间,设置一周
     */
    private  static final long EXPIRE = 60000 * 60 * 24 * 7;
    //private  static final long EXPIRE = 1;


    /**
     * 加密秘钥  自由设置
     */
    private  static final String SECRET = "xdclass.net168";


    /**
     * 令牌前缀  自由设置(在生成的令牌前加上前缀,也可不加)
     */
    private  static final String TOKEN_PREFIX = "xdclass";


    /**
     * subject 自由设置
     */
    private  static final String SUBJECT = "xdclass";


    /**
     * 根据用户信息,生成令牌
     * @param user
     * @return
     */
    public static String geneJsonWebToken(User user){

        String token = Jwts.builder().setSubject(SUBJECT)
                .claim("head_img",user.getHeadImg())
                .claim("id",user.getId())
                .claim("name",user.getName())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .signWith(SignatureAlgorithm.HS256,SECRET).compact();

        token = TOKEN_PREFIX + token;


        return token;
    }


    /**
     * 校验token的方法
     * @param token
     * @return
     */
    public static Claims checkJWT(String token){

        try{

            final  Claims claims = Jwts.parser().setSigningKey(SECRET)
                    .parseClaimsJws(token.replace(TOKEN_PREFIX,"")).getBody();

            return claims;

        }catch (Exception e){
            return null;
        }

    }



}
  • dao层
public interface UserMapper {

    int save(User user);

    User findByPhoneAndPwd(@Param("phone") String phone, @Param("pwd") String pwd);

}
<?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="net.xdclass.online_xdclass.mapper.UserMapper">


    <insert id="save" parameterType="User">

        INSERT  INTO user (name, pwd, head_img, phone , create_time)
        values (#{name,jdbcType=VARCHAR}, #{pwd,jdbcType=VARCHAR}, #{headImg,jdbcType=VARCHAR},
        #{phone,jdbcType=VARCHAR},#{createTime,jdbcType=TIMESTAMP})

    </insert>
    

    <!--根据手机号和密码找用户-->
    <select id="findByPhoneAndPwd" resultType="User">

        select  * from user where phone =#{phone} and pwd = #{pwd}


    </select>
  </mapper>

测试:
在这里插入图片描述

登录拦截器

SpringBoot实现登录拦截的原理

SpringBoot通过实现HandlerInterceptor接⼝实现拦截器,通过实现WebMvcConfigurer接⼝实现⼀个配置类,在配置类中注⼊拦截器,最后再通过@Configuration注解注⼊配置.

1.1、实现HandlerInterceptor接⼝

实现HandlerInterceptor接⼝需要实现3个⽅法:

  • preHandle
  • postHandle
  • afterCompletion
public class LoginInterceptor implements HandlerInterceptor {

    /**
     * 进入到controller之前的方法
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        try {

            String accessToken = request.getHeader("token");
            if (accessToken == null) {
                accessToken = request.getParameter("token");
            }

            if (StringUtils.isNotBlank(accessToken)) {
                Claims claims = JWTUtils.checkJWT(accessToken);
                if (claims == null) {
                    //告诉登录过期,重新登录
                    sendJsonMessage(response, JsonData.buildError("登录过期,重新登录"));
                    return false;
                }

                Integer id = (Integer) claims.get("id");
                String name = (String) claims.get("name");

                request.setAttribute("user_id", id);
                request.setAttribute("name", name);

                return true;

            }

        }catch (Exception e){}

        sendJsonMessage(response, JsonData.buildError("登录过期,重新登录"));

        return false;
    }


    /**
     * 响应json数据给前端
     * @param response
     * @param obj
     */
    public static void sendJsonMessage(HttpServletResponse response, Object obj){

        try{
            ObjectMapper objectMapper = new ObjectMapper();
            response.setContentType("application/json; charset=utf-8");
            PrintWriter writer = response.getWriter();
            writer.print(objectMapper.writeValueAsString(obj));
            writer.close();
            response.flushBuffer();
        }catch (Exception e){
            e.printStackTrace();
        }


    }


    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

preHandle在Controller之前执⾏,因此拦截器的功能主要就是在这个部分实现:

  • 检查session中是否有user对象存在;
  • 如果存在,就返回true,那么Controller就会继续后⾯的操作;
  • 如果不存在,就会重定向到登录界⾯。就是通过这个拦截器,使得Controller在执⾏之前,都执⾏⼀遍preHandle.

1.2、实现WebMvcConfigurer接⼝,注册拦截器

实现WebMvcConfigurer接⼝来实现⼀个配置类,将上⾯实现的拦截器的⼀个对象注册到这个配置类中.

/**
 * 拦截器配置
 *
 * 不用权限可以访问url    /api/v1/pub/
 * 要登录可以访问url    /api/v1/pri/
 */

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {


    @Bean
    LoginInterceptor loginInterceptor(){
        return new LoginInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(loginInterceptor()).addPathPatterns("/api/v1/pri/*/*/**")
                //不拦截哪些路径   斜杠一定要加
                .excludePathPatterns("/api/v1/pri/user/login","/api/v1/pri/user/register");

        WebMvcConfigurer.super.addInterceptors(registry);

    }
}

将拦截器注册到了拦截器列表中,并且指明了拦截哪些访问路径,不拦截哪些访问路径,不拦截哪些资源⽂件;最后再以@Configuration注解将配置注⼊。

1.3、保持登录状态

只需⼀次登录,如果登录过,下⼀次再访问的时候就⽆需再次进⾏登录拦截,可以直接访问⽹站⾥⾯的内容了。在正确登录之后,就将token保存到HTTP Header中,再次访问页⾯的时候,登录拦截器就可以找到这个token(token里包含了用户信息),就不需要再次拦截到登录界⾯了.

使用token个⼈信息查询接⼝开发

**简介:根据token查询个⼈信息接⼝开发 **
直接解密token,获取个⼈信息
通过token解密查询数据库获取个⼈信息

  • controller
@RestController
@RequestMapping("api/v1/pri/user")
public class UserController {


    @Autowired
    private UserService userService;

    /**
     * 注册接口
     *
     * @param userInfo
     * @return
     */
    @PostMapping("register")
    public JsonData register(@RequestBody Map<String, String> userInfo) {

        int rows = userService.save(userInfo);

        return rows == 1 ? JsonData.buildSuccess() : JsonData.buildError("注册失败,请重试");

    }


    /**
     * 登录接口
     *
     * @param loginRequest
     * @return
     */
    @PostMapping("login")
    public JsonData login(@RequestBody LoginRequest loginRequest) {

        String token = userService.findByPhoneAndPwd(loginRequest.getPhone(), loginRequest.getPwd());

        return token == null ? JsonData.buildError("登录失败,账号密码错误") : JsonData.buildSuccess(token);

    }


    /**
     * 根据用户id查询用户信息
     * @param request
     * @return
     */
    @GetMapping("find_by_token")
    public JsonData findUserInfoByToken(HttpServletRequest request){

        Integer userId = (Integer) request.getAttribute("user_id");

        if(userId == null){
            return JsonData.buildError("查询失败");
        }

        User user =  userService.findByUserId(userId);

        return JsonData.buildSuccess(user);

    }

}
  • service
public interface UserService {

    /**
     * 新增用户
     * @param userInfo
     * @return
     */
    int save(Map<String, String> userInfo);

    String findByPhoneAndPwd(String phone, String pwd);

    User findByUserId(Integer userId);
}
    @Override
    public User findByUserId(Integer userId) {

        User user = userMapper.findByUserId(userId);
        return user;
    }
  • DAO
public interface UserMapper {

    int save(User user);

    User findByPhoneAndPwd(@Param("phone") String phone, @Param("pwd") String pwd);

    User findByUserId(@Param("user_id") Integer userId);
}
<?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="net.xdclass.online_xdclass.mapper.UserMapper">


    <insert id="save" parameterType="User">

        INSERT  INTO user (name, pwd, head_img, phone , create_time)
        values (#{name,jdbcType=VARCHAR}, #{pwd,jdbcType=VARCHAR}, #{headImg,jdbcType=VARCHAR},
        #{phone,jdbcType=VARCHAR},#{createTime,jdbcType=TIMESTAMP})

    </insert>


    <!--根据手机号和密码找用户-->
    <select id="findByPhoneAndPwd" resultType="User">

        select  * from user where phone =#{phone} and pwd = #{pwd}


    </select>


    <select id="findByUserId" resultType="User">

      select  * from user where id=#{user_id}

    </select>

</mapper>
  • 测试

先登录获取token
在这里插入图片描述

Header中传入token
在这里插入图片描述

Header中不传token
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值