美文网首页
1-(1)、密码加密与微服务鉴权JWT

1-(1)、密码加密与微服务鉴权JWT

作者: 神奇作手 | 来源:发表于2019-08-26 09:33 被阅读0次

1、BCrypt密码加密

1.1、 准备工作

Ⅰ、任何应用考虑到安全,绝不能明文的方式保存密码。密码应该通过哈希算法进行加密。
Ⅱ、Spring Security 提供了BCryptPasswordEncoder类,实现Spring的PasswordEncoder接口使用BCrypt强哈希方法来加密密码。
Ⅲ、BCrypt强哈希方法 每次加密的结果都不一样。

(1)user工程的pom引入依赖
  <!-- security -->
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
  </dependency>
(2)添加配置类

   在添加了 spring security 依赖后,所有的地址都被 spring security 所控制了,我们目前只是需要用到 BCrypt 密码加密的部分,所以我们要添加一个配置类,配置为所有地址都可以匿名访问。

/**
 * 安全配置类
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    /**
     * authorizeRequests:所有security全注解配置实现的开端,表示开始说明需要的权限;
     *                    需要的权限分两部分,第一部分是拦截的路径,第二部分访问该路径需要的权限;
     * antMatchers:表示拦截什么路径,permitAll 任何权限都可以访问,直接放行所有;
     * anyRequest():任何请求,authenticated 认证后才能访问;
     * .and().csrf().disable():固定写法,表示使csrf拦截失效。
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/**").permitAll()
                .anyRequest().authenticated()
                .and().csrf().disable();
    }
}
(3)修改user工程的Application, 配置bean
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

1.2、管理员密码加密

1.2.1 新增管理员密码加密
 修改user工程的AdminService
    @Autowired
    private BCryptPasswordEncoder encoder;

    /**
     * 增加
     * @param admin
     */
    public void add(Admin admin) {
        admin.setId( idWorker.nextId()+"" );
        //密码加密
        admin.setPassword(encoder.encode(admin.getPassword()));
        adminDao.save(admin);
    }
1.2.2、管理员登陆密码校验
(1)AdminDao增加方法定义
    //根据用户名查询
    public Admin findByLoginname(String loginname);
(2)AdminService增加方法
    /*
     * @Description: //TODO 用户登录
     * @Param: [admin]
     * @return: com.tensquare.user.pojo.Admin
     */
    public Admin login(Admin admin) {
        //现根据用户名查询对象
        Admin adminLogin = adminDao.findByLoginname(admin.getLoginname());
        //用数据库中查询出来得密码和用户输入得密码比对
        if (adminLogin != null && encoder.matches(admin.getPassword(),adminLogin.getPassword())){
            //登录成功
            return adminLogin;
        }
        //登录失败
        return null;
    }
(3)AdminController增加方法
    /**
     * @Description: //TODO 用户登录
     * @Param: []
     * @return: entity.Result
     */
    @PostMapping("/login")
    public Result login(@RequestBody Admin admin){
        admin = adminService.login(admin);
        if (admin == null){
            return new Result(false, StatusCode.LOGINERROR, "登录失败");
        }
        //使得前后端通话,后续完善
        return new Result(true, StatusCode.OK, "登录成功", map);
    }

1.3、用户密码加密

1.3.1、用户注册密码加密

(1)、修改user工程的UserService 类,引入BCryptPasswordEncoder
    @Autowired
    private BCryptPasswordEncoder encoder;
(2)、修改user工程的UserService 类的add方法,添加密码加密的逻辑
    /**
     * 增加
     * @param user
     */
    public void add(User user) {
        user.setId( idWorker.nextId()+"" );
        //密码加密
        user.setPassword(encoder.encode(user.getPassword()));
        userDao.save(user);
    }
(3)测试运行后,添加数据
  {
     "mobile": "13901238899" ,
     "password": "123456"
  }

数据库中的密码为以下形式

    $2a$10$a/EYRjdKwQ6zjr0/HJ6RR.rcA1dwv1ys7Uso1xShUaBWlIWTyJl5S

1.3.2 用户登陆密码判断

(1)修改user工程的UserDao接口,增加方法定义
   //根据用户名查询
    public User findByMobile(String mobile);
(2)修改user工程的UserService 类,增加方法
   /**
     * 增加
     * @param user
     */
    public void add(User user) {
        user.setId( idWorker.nextId()+"" );
        //密码加密
        user.setPassword(encoder.encode(user.getPassword()));
        userDao.save(user);
    }
(3)修改tensquare_user工程的UserController类,增加login方法
    /*
     * @Description: //TODO 用户登录
     * @Param: [user]
     * @return: entity.Result
     */
    @PostMapping("/login")
    public Result login(@RequestBody User user){
        user = userService.login(user.getMobile(), user.getPassword());
        if (user == null){
            return new Result(false, StatusCode.LOGINERROR, "登录失败");
        }
        return new Result(true, StatusCode.OK, "登录成功", map);
    }
(4)使用刚才新增加的账号进行测试,查看返回结果

2 常见的认证机制

2.1Token Auth

使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是 这样的:

  1. 客户端使用用户名跟密码请求登录;
  2. 服务端收到请求,去验证用户名与密码;
  3. 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端;
  4. 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里 ;
  5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token;
  6. 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向 客户端返回请求的数据;
Token Auth的优点
 Token机制相对于Cookie机制又有什么好处呢?
  • 支持跨域访问: 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).

3 基于JWT的Token认证机制实现

3.1、什么是JWT

  JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用 户和服务器之间传递安全可靠的信息。

3.2、JWT组成

  由三部分组成,头部、载荷签名。
(1)、头部(Header)

  头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。可以被表示成一个JSON对象。

  {"typ":"JWT","alg":"HS256"}

在头部指明了签名算法是HS256算法。 进行BASE64编 码,编码后的字符串如下:

   eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

(2)、载荷(playload)

载荷就是存放有效信息的地方。这些有效信息包 含三个部分:

  • 标准中注册的声明(建议但不强制使用)

iss: jwt签发者
sub:jwt所面向的用户
aud:接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间

nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

  • 公共的声明

   公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息。但不建议添加敏感信息,因为该部分在客户端可解密。

  • 私有的声明

   私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64 是对称解密的,意味着该部分信息可以归类为明文信息。

定义一个payload:

   {"sub":"1234567890","name":"John Doe","admin":true}

然后将其进行base64编码,得到Jwt的第二部分:

  eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

(3)、签证(signature)
   jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

header (base64后的)
payload (base64后的)
secret

  这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符 串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第 三部分。

   TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

   将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6I kpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用 来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流 露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

4、Java的JJWT实现JWT

4.1、什么是JJWT

 JJWT是一个提供端到端的JWT创建和验证的Java库。

4.2、JJWT使用

4.2.1、token的创建

(1)创建maven工程,引入依赖
      <!--jjwt-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>
(2)创建类CreateJwtTest,用于生成token
public class CreatJwt {

    public static void main(String[] args) {
        JwtBuilder jwtBuilder = Jwts.builder()
                .setId("666") //用户ID
                .setSubject("小明") //用户名
                .setIssuedAt(new Date()) //登录时间
                .signWith(SignatureAlgorithm.HS256, "justIT") //签名
                .setExpiration(new Date(new Date().getTime()+300000)//设置过期时间
                .claim("roles","admin"); //自定义项

        System.out.println(jwtBuilder.compact());
    }
}

setIssuedAt用于设置签发时间;
setExpiration用于设置过期时间;
signWith用于设置签名秘钥;
claim用于设置自定义项;

  有很多时候,我们并不希望签发的token是永久生效的,所以我们会为token添加一个 过期时间setExpiration。
  当未过期时可以正常读取,当过期时会引发 io.jsonwebtoken.ExpiredJwtException异常。
  如果想存储更多的信息(例如角 色)可以定义自定义claims。

(3)测试运行,输出如下:
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI2NjYiLCJzdWIiOiLlsI_mmI4iLCJpYXQiOjE1NjY3ODI3MzMsImV4cCI6MTU2Njc4MzAzMywicm9sZXMiOiJhZG1pbiJ9.GZieJm3zfJgu2cerYS0zIsCphdAjQm70gT-l39yxP2I

再次运行,会发现每次运行的结果是不一样的,因为我们的载荷中包含了时间。

4.2.2、token的解析

   我们刚才已经创建了token ,在web应用中这个操作是由服务端进行然后发给客户 端,客户端在下次向服务端发送请求时需要携带这个token(这就好像是拿着一张门票一 样),那服务端接到这个token 应该解析出token中的信息(例如用户id),根据这些信息 查询数据库返回相应的结果。

(1)创建ParseJwt
public class ParseJwt {

    public static void main(String[] args) {
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI2NjYiLCJzdWIiOiLlsI_mmI4iLCJpYXQiOjE1NjY3ODI3MzMsImV4cCI6MTU2Njc4MzAzMywicm9sZXMiOiJhZG1pbiJ9.GZieJm3zfJgu2cerYS0zIsCphdAjQm70gT-l39yxP2I";
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey("justIT")
                    .parseClaimsJws(token)
                    .getBody();
            System.out.println(claims);
            System.out.println("用户Id:"+claims.getId());
            System.out.println("用户名称:"+claims.getSubject());
            System.out.println("登录时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(claims.getIssuedAt()));
            System.out.println("过期时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(claims.getExpiration()));
            System.out.println("用户角色:"+claims.get("roles"));

        }catch (Exception e){
            System.out.println("签名失效");
        }
    }
}


后文继续.......

相关文章

网友评论

      本文标题:1-(1)、密码加密与微服务鉴权JWT

      本文链接:https://www.haomeiwen.com/subject/xxflectx.html