用户登录并返回token(springboot)

何为token?【如果想直接看代码可以往下翻】

使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:
1. 客户端使用用户名跟密码请求登录
2. 服务端收到请求,去验证用户名与密码
3. 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
4. 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里
5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
6. 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据

token优点

支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通
过HTTP头传输.
无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登
录用户的信息,只需要在客户端的cookie或本地介质存储状态信息.
更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的服
务端只要提供API即可.
去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你
可以进行Token生成调用即可.
更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 8等)时,Cookie是不被支持的
(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多。
CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。
性能: 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256计算 的Token验证和解析
要费时得多.
不需要为登录页面做特殊处理: 如果你使用Protractor 做功能测试的时候,不再需要为登录页面做特殊处理.
基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库(.NET, Ruby,
Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft)

如何创建token【我这里用的是Jwt机制,有些公司用的是自己的生成方法哈】

(1)创建maven工程,引入依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.6.0</version>
</dependency>

(2)使用工具类JwtUtils

/**
* @author: Mr.Yang
* @create: 2020-02-13 21:19
**/
@Getter
@Setter
@ConfigurationProperties("jwt.config")
public class JwtUtils {
//签名私钥
private String key;
//签名失效时间
private Long failureTime;

/**
* 设置认证token
*
* @param id 用户登录ID
* @param subject 用户登录名
* @param map 其他私有数据
* @return
*/
public String createJwt(String id, String subject, Map<String, Object> map) {

//1、设置失效时间啊
long now = System.currentTimeMillis(); //毫秒
long exp = now + failureTime;

//2、创建JwtBuilder
JwtBuilder jwtBuilder = Jwts.builder().setId(id).setSubject(subject)
.setIssuedAt(new Date())
//设置签名防止篡改
.signWith(SignatureAlgorithm.HS256, key);

//3、根据map设置claims
for (Map.Entry<String, Object> entry : map.entrySet()) {
jwtBuilder.claim(entry.getKey(), entry.getValue());
}
jwtBuilder.setExpiration(new Date(exp));

//4、创建token
String token = jwtBuilder.compact();
return token;
}

/**
* 解析token
*
* @param token
* @return
*/
public Claims parseJwt(String token) {
Claims claims = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
return claims;
}

}

复制代码
 1 /**
 2  * @author: Mr.Yang
 3  * @create: 2020-02-13 21:19
 4  **/
 5 @Getter
 6 @Setter
 7 @ConfigurationProperties("jwt.config")
 8 public class JwtUtils {
 9     //签名私钥
10     private String key;
11     //签名失效时间
12     private Long failureTime;
13 
14     /**
15      * 设置认证token
16      *
17      * @param id      用户登录ID
18      * @param subject 用户登录名
19      * @param map     其他私有数据
20      * @return
21      */
22     public String createJwt(String id, String subject, Map<String, Object> map) {
23 
24         //1、设置失效时间啊
25         long now = System.currentTimeMillis();  //毫秒
26         long exp = now + failureTime;
27 
28         //2、创建JwtBuilder
29         JwtBuilder jwtBuilder = Jwts.builder().setId(id).setSubject(subject)
30                 .setIssuedAt(new Date())
31                 //设置签名防止篡改
32                 .signWith(SignatureAlgorithm.HS256, key);
33 
34         //3、根据map设置claims
35         for (Map.Entry<String, Object> entry : map.entrySet()) {
36             jwtBuilder.claim(entry.getKey(), entry.getValue());
37         }
38         jwtBuilder.setExpiration(new Date(exp));
39 
40         //4、创建token
41         String token = jwtBuilder.compact();
42         return token;
43     }
44 
45     /**
46      * 解析token
47      *
48      * @param token
49      * @return
50      */
51     public Claims parseJwt(String token) {
52         Claims claims = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
53         return claims;
54     }
55 
56 }
复制代码

注意:

@ConfigurationProperties("jwt.config")需要在配置文件中配置

(3)配置JwtUtils类
复制代码
1     /**
2      * 配置jwt
3      *
4      * @return
5      */
6     @Bean
7     public JwtUtils jwtUtils() {
8         return new JwtUtils();
9     }
复制代码

(4)编写登录方法

  1、编写DAO层

/**
* @author: Mr.Yang
* @create: 2020-02-13 21:55
**/
@Repository
public interface UserDAO {

public User selectByMobileUser(String mobile);

public User selectByIdUser(String id);
}

复制代码
 1 /**
 2  * @author: Mr.Yang
 3  * @create: 2020-02-13 21:55
 4  **/
 5 @Repository
 6 public interface UserDAO {
 7 
 8     public User selectByMobileUser(String mobile);
 9 
10     public User selectByIdUser(String id);
11 }
复制代码

  2、编写xml写Sql

<select id="selectByMobileUser" parameterType="string" resultMap="userMap">
        select *
        from bs_user
        where mobile = #{mobile}
    </select>
 <select id="selectByMobileUser" parameterType="string" resultMap="userMap">
        select *
        from bs_user
        where mobile = #{mobile}
    </select>

  3、编写service层

/**
* 根据mobile查询用户
*
* @param mobile
* @return
*/
public User selectByMobile(String mobile) {
return userDAO.selectByMobileUser(mobile);
}

复制代码
 /**
     * 根据mobile查询用户
     *
     * @param mobile
     * @return
     */
    public User selectByMobile(String mobile) {
        return userDAO.selectByMobileUser(mobile);
    }
复制代码

  4、编写controller层

/**
* 用户登录
* 1.通过service根据mobile查询用户
* 2.比较password
* 3.生成jwt信息
*
* @param loginMap
* @return
* @requestBody把请求数据封装(前端以json方式传)
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
public Result login(@RequestBody Map<String, String> loginMap) {
String mobile = loginMap.get("mobile");
String password = loginMap.get("password");
User user = userService.selectByMobile(mobile);
//登录失败
if (user == null || !user.getPassword().equals(password)) {
//既可以使用抛异常,也可使用直接返回错误码(推荐)
return new Result(ResultCode.MOBILEORPASSWORDERROR);
} else {
//其他数据以map集合存放在token中
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("companyId", user.getCompanyId());
dataMap.put("companyName", user.getCompanyName());
//生成token并存入数据返回
String token = jwtUtils.createJwt(user.getId(), user.getUsername(), dataMap);
return new Result(Result.SUCCESS(), token);
}
}

复制代码
 1  /**
 2      * 用户登录
 3      * 1.通过service根据mobile查询用户
 4      * 2.比较password
 5      * 3.生成jwt信息
 6      *
 7      * @param loginMap
 8      * @return
 9      * @requestBody把请求数据封装(前端以json方式传)
10      */
11     @RequestMapping(value = "/login", method = RequestMethod.POST)
12     public Result login(@RequestBody Map<String, String> loginMap) {
13         String mobile = loginMap.get("mobile");
14         String password = loginMap.get("password");
15         User user = userService.selectByMobile(mobile);
16         //登录失败
17         if (user == null || !user.getPassword().equals(password)) {
18             //既可以使用抛异常,也可使用直接返回错误码(推荐)
19             return new Result(ResultCode.MOBILEORPASSWORDERROR);
20         } else {
21             //其他数据以map集合存放在token中
22             Map<String, Object> dataMap = new HashMap<>();
23             dataMap.put("companyId", user.getCompanyId());
24             dataMap.put("companyName", user.getCompanyName());
25             //生成token并存入数据返回
26             String token = jwtUtils.createJwt(user.getId(), user.getUsername(), dataMap);
27             return new Result(Result.SUCCESS(), token);
28         }
29     }
复制代码

  5、测试

data就是返回的token,里面存有用户ID,用户姓名及以map形式存入的数据(这里主要看前端需要什么就存什么)


(6)用户登录成功之后,获取用户信息

/**
* 用户登录成功之后,获取用户信息
* 1.获取用户id
* 2.根据用户id查询用户
* 3.构建返回值对象
* 4.响应
*
* @param request
* @return
* @throws Exception
*/
@RequestMapping(value = "/profile", method = RequestMethod.POST)
public Result profile(HttpServletRequest request) throws PendingException, Exception {

/**
* 从请求头信息中获取token数据
* 1.获取请求头信息:名称=Authorization(前后端约定)
* 2.替换Bearer+空格
* 3.解析token
* 4.获取clamis
*/


//1.获取请求头信息:名称=Authorization(前后端约定)
String authorization = request.getHeader("Authorization");
if (StringUtils.isEmpty(authorization)) {
// throw new PendingException(ResCode.UNAUTHENTICATED);
//系统未捕捉到请求头信息
throw new CommonException(ResultCode.UNAUTHENTICATED);
}
//2.替换Bearer+空格
String token = authorization.replace("Bearer ", "");

//3.解析token
Claims claims = jwtUtils.parseJwt(token);
//4.获取clamis
String userId = claims.getId();

// String userId = "U01";
User user = userService.selectByIdUser(userId);

/**此处只是为了获取token中的用户数据,所有只简单返回用户对象,
* 工作则按实际要求多表查询需要数据(根据用户ID查询权限)
*/

return new Result(ResultCode.SUCCESS, user);
}

复制代码
 1  /**
 2      * 用户登录成功之后,获取用户信息
 3      * 1.获取用户id
 4      * 2.根据用户id查询用户
 5      * 3.构建返回值对象
 6      * 4.响应
 7      *
 8      * @param request
 9      * @return
10      * @throws Exception
11      */
12     @RequestMapping(value = "/profile", method = RequestMethod.POST)
13     public Result profile(HttpServletRequest request) throws PendingException, Exception {
14 
15         /**
16          * 从请求头信息中获取token数据
17          *   1.获取请求头信息:名称=Authorization(前后端约定)
18          *   2.替换Bearer+空格
19          *   3.解析token
20          *   4.获取clamis
21          */
22 
23 
24         //1.获取请求头信息:名称=Authorization(前后端约定)
25         String authorization = request.getHeader("Authorization");
26         if (StringUtils.isEmpty(authorization)) {
27 //            throw new PendingException(ResCode.UNAUTHENTICATED);
28             //系统未捕捉到请求头信息
29             throw new CommonException(ResultCode.UNAUTHENTICATED);
30         }
31         //2.替换Bearer+空格
32         String token = authorization.replace("Bearer ", "");
33 
34         //3.解析token
35         Claims claims = jwtUtils.parseJwt(token);
36         //4.获取clamis
37         String userId = claims.getId();
38 
39         //  String userId = "U01";
40         User user = userService.selectByIdUser(userId);
41 
42         /**此处只是为了获取token中的用户数据,所有只简单返回用户对象,
43          * 工作则按实际要求多表查询需要数据(根据用户ID查询权限)
44          */
45 
46         return new Result(ResultCode.SUCCESS, user);
47     }
复制代码

(7)测试

 

 
 
三、 token的过滤

先拦截所有需要被过滤的HTTP请求,再获取HTTP的head中携带的token字符串,在对其进行解析。

如何拦截固定的HTTP请求可以点击:这里

代码的主要逻辑是:

首先拦截http请求,获取head中的token,解析token,得到token的状态码code,然后在httpServletResponse的头中添加code后,继续执行代码,到Controller的方法体中,获取response中的code并根据code进行判断当前token的状态,进行相对应的处理。其他情况也是类似。

@WebFilter(urlPatterns = {"/app/*"})
public class TokenAuthorFilter implements Filter {
   private static Logger logger = LoggerFactory.getLogger(TokenAuthorFilter.class);
   @Autowired
   private JWTService jwtService;


   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
      HttpServletRequest req = (HttpServletRequest) request;
      HttpServletResponse rep = (HttpServletResponse) response;

      //设置允许跨域的配置
      // 这里填写你允许进行跨域的主机ip(正式上线时可以动态配置具体允许的域名和IP)
      rep.setHeader("Access-Control-Allow-Origin", "*");
      // 允许的访问方法
      rep.setHeader("Access-Control-Allow-Methods","POST, GET");
      // Access-Control-Max-Age 用于 CORS 相关配置的缓存
//    rep.setHeader("Access-Control-Max-Age", "3600");
      rep.setHeader("Access-Control-Allow-Headers","token");
//    rep.setHeader("Access-Control-Allow-Headers","token,Origin, X-Requested-With, Content-Type, Accept");


      rep.setCharacterEncoding("UTF-8");
      rep.setContentType("application/json; charset=utf-8");
      String token = req.getHeader("token");//header方式
    String method = ((HttpServletRequest) request).getMethod();


      if (method.equals("OPTIONS")) {
         rep.setStatus(HttpServletResponse.SC_OK);
      }else{


         if (null == token || token.isEmpty()) {
            logger.info("用户授权认证没有通过!客户端请求参数中无token信息");
            rep.setHeader("code","100");
            rep.setHeader("msg","用户授权认证没有通过!客户端请求参数中无token信息");
            chain.doFilter(req, rep);
         } else {
            String exception=JavaWebTokenUtil.parseJWT(token).toString();
            if (exception!=null){
               if (ExpiredJwtException.class.getName().equals(exception)){
                  logger.info("token已过期!");
                  rep.setHeader("code","100");
                  rep.setHeader("msg","token is overtime!");
                  chain.doFilter(req, rep);
               }else if (SignatureException.class.getName().equals(exception)){
                  System.out.println("token sign解析失败");
                  rep.setHeader("code","100");
                  rep.setHeader("msg","token is invalid! ");
                  chain.doFilter(req, rep);
               }else if (MalformedJwtException.class.getName().equals(exception)){
                  System.out.println("token的head解析失败");
                  rep.setHeader("code","100");
                  rep.setHeader("msg","token is invalid! ");
                  chain.doFilter(req, rep);
               }else if ("success".equals(exception)){
                  logger.info("用户授权认证通过!");
                  rep.setHeader("code","0");
                  chain.doFilter(req, rep);
               }else {
                     logger.info("token的有效期小与2天!");
                     String newToken=jwtService.updateToken(exception);
                     logger.info("已生成新的token:"+newToken);
                     rep.setHeader("code","1");
//                   rep.setHeader("msg","token will invalid at 2 days!please refresh");
                     rep.setHeader("newToken",newToken);
                     chain.doFilter(req, rep);
                  }
               }

            }

         }



   }

   @Override
   public void destroy() {

   }

   @Override
   public void init(FilterConfig filterConfig) throws ServletException {

      logger.info("初始化filter");
   }
}

  

 

Controller中的代码:

 

@ResponseBody
   @GetMapping("app/refresh")
   public Object refresh(HttpServletRequest request, HttpServletResponse response) {
   Result result=appControllerService.codeFilter(response);
   System.out.println(result.getCode());
   System.out.println(result.getMsg());
   Token token=(Token) result.getData();
   if (token!=null){
      System.out.println(token.getToken());
   }

   return result;
}

  

public Result codeFilter(HttpServletResponse response){
      String code = response.getHeader("code");
      logger.info("code:" + code);
//    String msg = response.getHeader("msg");
//    logger.info(msg);
      if (code.equals("0")) {            //如果code为空,则说明对token认证成功
         return ResultUtil.success1();
      } else if (code.equals("1")) {    //code是1 表示:token解析成功,但是token即将过期,需要更换新的token
         String newToken = response.getHeader("newToken");
         //把更新的token保存到数据库的user表中。
         Integer id=tokenUtil.getCurrentUserId(newToken);
         User user1=new User();
         user1=userRepo.findOne(id);
         System.out.println(id);
         user1.setId(id);
         user1.setToken(newToken);
         userRepo.save(user1);
         logger.info("token已经被替换!");
         Token token = new Token();
         token.setToken(newToken);
         return ResultUtil.error(1, "success", token);
      } else if (code.equals("100")) {   //code是100 表示: token解析失败
         return ResultUtil.error(100, "error", null);
      } else {
         return ResultUtil.error(103, "error", null);
      }

   }

 

posted @ 2022-04-06 11:15  满Sir  阅读(5631)  评论(0编辑  收藏  举报