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

2019-08-26  本文已影响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机制又有什么好处呢?

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("签名失效");
        }
    }
}


后文继续.......

上一篇下一篇

猜你喜欢

热点阅读