美文网首页
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