BCrypt 密码加密

  任何应用考虑到安全,绝不能明文的方式保存密码。密码应该通过哈希算法进行加密。 有很多标准的算法比如 SHA(微信公众号使用) 或者 MD5,结合 salt(盐)是一个不错的选择。 Spring Security 提 供了 BCryptPasswordEncoder 类,实现 Spring 的 PasswordEncoder 接口使用 BCrypt 强哈希方法 来加密密码。 BCrypt 强哈希方法 每次加密的结果都不一样。

BCrypt 密码加密准备工作

  任何应用考虑到安全,绝不能明文的方式保存密码。密码应该通过哈希算法进行加密。 有很多标准的算法比如 SHA 或者 MD5,结合 salt(盐)是一个不错的选择。 Spring Security 提 供了 BCryptPasswordEncoder 类,实现 Spring 的 PasswordEncoder 接口使用 BCrypt 强哈希方法 来加密密码。
  BCrypt 强哈希方法 每次加密的结果都不一样。

实现流程

导入依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/**").permitAll()
                .anyRequest().authenticated()
                .and().csrf().disable();
    }
}

配置 初始化bean

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@SpringBootApplication
public class UserApplication {

    public static void main(String[] args) {

    SpringApplication.run(UserApplication.class);
    }


    @Bean
    public IdWorker getIdWorker(){
        return new IdWorker(1,1);
    }

    /***
     * BCrypt 密码加密初始化
     * @return
     */
    @Bean
    public BCryptPasswordEncoder bcryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

}

实体类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Entity
@Table(name="tb_admin")
public class Admin implements Serializable{

	@Id
	private String id;//ID


	
	private String loginname;//登陆名称
	private String password;//密码
	private String state;//状态

	
	public String getId() {		
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}

	public String getLoginname() {		
		return loginname;
	}
	public void setLoginname(String loginname) {
		this.loginname = loginname;
	}

	public String getPassword() {		
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}

	public String getState() {		
		return state;
	}
	public void setState(String state) {
		this.state = state;
	}


	
}

添加用户并加密
controller

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@RestController
@CrossOrigin
@RequestMapping("admin")
public class AdminController {

    @Autowired
   private BCryptPasswordEncoder bCryptPasswordEncoder;
    @Autowired
    private IdWorker idWorker;


    @Autowired
    private AdminService adminService;

    /**
     * 添加管理员用户并加密
     * @param admin
     * @return
     */
    @RequestMapping(method = RequestMethod.POST)
    public Result addPassWord(@RequestBody Admin admin){


        admin.setId(idWorker.nextId()+"");
        //将id进行加密,然后再添加回去
        admin.setPassword(bCryptPasswordEncoder.encode(admin.getPassword()));

        adminService.addAdmin(admin);

        return new Result(true, StatusCode.OK,"添加admin用户成功");
    }
	}

service

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@Service
public class AdminService {

    @Autowired
    private AdminDao adminDao;
    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;
    @Autowired
    private IdWorker idWorker;

    /**
     * 添加用户
     * @param admin
     */
    public  void  addAdmin(Admin admin){

        adminDao.save(admin);
    }
}

登录并验证
controller

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 @RequestMapping(value = "/login",method = RequestMethod.POST)
    public Result login(@RequestBody Map<String,String> map){

       Admin admin=   adminService.login(map.get("loginname"),map.get("password"));

       //有这个用户,并且用户的密码正确匹配
       if (admin!=null&&bCryptPasswordEncoder.matches(map.get("password"),admin.getPassword())){

        return  new Result(true,StatusCode.OK,"登录成功");

       }else {

        return  new Result(false,StatusCode.ERROR,"登录失败");
       }

service

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
 /**
     * 登录
     * @param loginName
     * @return
     */
    public Admin login(String loginName,String password){

        //密码加密
        //根据用户名查询用户
       Admin admin= adminDao.findByLoginname( loginName);
       //判断用户是否存在并且密码是否正确
        if (admin!=null&&bCryptPasswordEncoder.matches(password,admin.getPassword())){

        return  admin;
        }
        return null;
    }

dao

1
2
3
4
5
6
7
8
9
public interface AdminDao extends JpaRepository<Admin ,String>,JpaSpecificationExecutor<Admin> {

    /**
     * 根据用户名进行查找
     * @param loginname
     * @return
     */
    public Admin findByLoginname(String loginname);
}

常见的认证机制

Http Basic Auth

  HTTP Basic Auth 简单点说明就是每次请求 API 时都提供用户的 username 和 password,简言之,Basic Auth 是配合 RESTful API 使用的最简单的认证方式,只需提供用户 名密码即可,但由于有把用户名密码暴露给第三方客户端的风险,在生产环境下被使用的越 来越少。因此,在开发对外开放的 RESTful API 时,尽量避免采用 HTTP BasicAuth

  Cookie 认证机制就是为一次请求认证在服务端创建一个 Session 对象,同时在客户端的浏览 器端创建了一个 Cookie 对象;通过客户端带上来 Cookie 对象来与服务器端的 session 对象匹 配来实现状态管理的。默认的,当我们关闭浏览器的时候,cookie 会被删除。但可以通过修 改 cookie 的 expire time 使 cookie 在一定时间内有效; ![alt baseed Auth](/img/baseed Auth.png)

OAuth

  OAuth(开放授权)是一个开放的授权标准,允许用户让第三方应用访问该用户 在某一 web 服务上存储的私密的资源(如照片,视频,联系人列表),而无需 将用户名和密码提供给第三方应用。 Auth 允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务 提供者的数据。每一个令牌授权一个特定的第三方系统(例如,视频编辑网站) 在特定的时段(例如,接下来的 2 小时内)内访问特定的资源(例如仅仅是某一 相册中的视频)。这样,OAuth 让用户可以授权第三方网站访问他们存储在另外 服务提供者的某些特定信息,而非所有内容 下面是 OAuth2.0 的流程: alt token认证 这种基于 OAuth 的认证机制适用于个人消费者类的互联网产品,如社交类 APP 等应用,但 是不太适合拥有自有认证权限管理的企业应用$\color{red}{比如一个网站可以允许你qq登录}$

Token Auth

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

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

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).

基于 JWT 的 Token 认证机制实现

什么是 JWT

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

JWT 组成

一个 JWT 实际上就是一个字符串,它由三部分组成,头部、载荷与签名。

头部(Header)

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

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

在头部指明了签名算法是 HS256 算法。 我们进行 BASE64 编 码 http://base64.xpcha.com/,编码后的字符串如下:

1
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

小知识:Base64 是一种基于 64 个可打印字符来表示二进制数据的表示方法。由于 2 的 6 次方等于 64,所以每 6 个比特为一个单元,对应某个可打印字符。三个字节有 24 个比特,对应于 4 个 Base64 单元,即 3 个字节需要用 4 个可打印字符来表示。JDK 中 提供了非常方便的 BASE64Encoder 和 BASE64Decoder,用它们可以非常方便的 完成基于 BASE64 的编码和解码

载荷(playload)

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

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

1
2
3
4
5
6
7
iss: jwt 签发者
sub: jwt 所面向的用户
aud: 接收 jwt 的一方
exp: jwt 的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该 jwt 都是不可用的.
iat: jwt 的签发时间
jti: jwt 的唯一身份标识,主要用来作为一次性 token,从而回避重放攻击。

公共的声明

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

私有的声明

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为 base64 是对称解密的,意味着该部分信息可以归类为明文信息。
这个指的就是自定义的 claim。比如前面那个结构举例中的 admin 和 name 都属于自定的 claim。
这些 claim 跟 JWT 标准规定的 claim 区别在于:JWT 规定的 claim,JWT 的接收方 在拿到 JWT 之后,都知道怎么对这些标准的 claim 进行验证(还不知道是否能够验证);而 private claims 不会验证,除非明确告诉接收方要对这些 claim 进行验证以及规则才行。
定义一个 payload:

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

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

1
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

签证(signature)

jwt 的第三部分是一个签证信息,这个签证信息由三部分组成:

1
2
3
header (base64 后的)
payload (base64 后的)
secret

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

1
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

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

1
2
3
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZS
I6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFO
NFh7HgQ

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

JWT结构

头部(Header)
载荷(playload)
标准中注册的声明(建议但不强制使用)
公共的声明
私有的声明
签证(signature)
header (base64 后的)
payload (base64 后的)
secret

Java 的 JJWT 实现 JWT

什么是 JJWT

  JJWT 是一个提供端到端的 JWT 创建和验证的 Java 库。永远免费和开源(Apache License,版本 2.0),JJWT 很容易使用和理解。它被设计成一个以建筑为中心的流 畅界面,隐藏了它的大部分复杂性。

JJWT 快速入门

导入依赖

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

创建JWT

1
2
3
4
5
6
7
8
9
public class CreateJwtTest {
    public static void main(String[] args) {
        JwtBuilder builder= Jwts.builder().setId("888")
                .setSubject("小白")
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS256,"java1995");
        System.out.println(builder.compact());
    }
}

setIssuedAt 用于设置签发时间
signWith 用于设置签名秘钥
测试运行,输出如下:

1
2
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1Mj
M0MTM0NTh9.gq0J‐cOM_qCNqU_s‐d_IrRytaNenesPmqAIhQpYXHZk

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

Token 的解析

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

1
2
3
4
5
6
7
8
public class ParseJwtTest {
    public static void main(String[] args) {
        String token ="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1OTUyMTQ5NjZ9.t_hcK4Z5c0D8KGayQ6PI1mmD1zazd9ibHpB0KAEpZfk";
        Claims claims = Jwts.parser().setSigningKey("java1995").parseClaimsJws(token).getBody();
        System.out.println("id"+claims.getId());
        System.out.println("sub"+claims.getSubject());
        System.out.println("Issue"+claims.getIssuedAt());
    }

$\color{red}{试着将 token 或签名秘钥篡改一下,会发现运行时就会报错,所以解析 token 也就是验证token}$

Token 过期校验

有很多时候,我们并不希望签发的 token 是永久生效的,所以我们可以为 token 添加一个过期时间。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class CreateJwtTest {
    public static void main(String[] args) {

        //获得当前系统时间毫秒值
        long timeMillis = System.currentTimeMillis();
        //设置一分钟后过期
        long time = timeMillis + 1000 * 60;

        JwtBuilder builder= Jwts.builder().setId("888")
                .setSubject("小白")
                .setIssuedAt(new Date())
                //设置过期时间
                .setExpiration(new Date(time) )
                .signWith(SignatureAlgorithm.HS256,"java1995");
        System.out.println(builder.compact());
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class ParseJwtTest {
    public static void main(String[] args) {
        String token ="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1OTUyMTU3NjIsImV4cCI6MTU5NTIxNTgyMn0.idursgwFyKjeW3p2d9zIwrDHaU0URjfgBIvWSEHTBGQ";
        Claims claims = Jwts.parser().setSigningKey("java1995").parseClaimsJws(token).getBody();
        System.out.println("id"+claims.getId());
        System.out.println("sub"+claims.getSubject());
        System.out.println("Issue"+claims.getIssuedAt());
        System.out.println("设置过期时间:"+claims.getExpiration());
    }
}

自定义claims

我们刚才的例子只是存储了 id 和 subject 两个信息,如果你想存储更多的信息(例如角色)可以定义自定义 claims

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class CreateJwtTest {
    public static void main(String[] args) {

        //获得当前系统时间毫秒值
        long timeMillis = System.currentTimeMillis();
        //设置一分钟后过期
        long time = timeMillis + 1000 * 60;

        JwtBuilder builder= Jwts.builder().setId("888")
                .setSubject("小白")
                .setIssuedAt(new Date())
                //设置过期时间
                .setExpiration(new Date(time) )

                //自定义角色
                .claim("user","jeremy")
                .claim("admin","jeremy SZE")
                .signWith(SignatureAlgorithm.HS256,"java1995");
        System.out.println(builder.compact());
    }
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class ParseJwtTest {
    public static void main(String[] args) {
        String token ="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1OTUyMTYxODYsImV4cCI6MTU5NTIxNjI0NiwidXNlciI6ImplcmVteSIsImFkbWluIjoiamVyZW15IFNaRSJ9.otOOQRFMfwHm67KcaFqkV8Oirer4Y0vJV7T7QyLPPD8";
        Claims claims = Jwts.parser().setSigningKey("java1995").parseClaimsJws(token).getBody();
        System.out.println("id"+claims.getId());
        System.out.println("sub"+claims.getSubject());
        System.out.println("Issue"+claims.getIssuedAt());

        System.out.println("设置过期时间:"+claims.getExpiration());

        //获取自定义角色
        System.out.println("user="+claims.get("user"));
        System.out.println("admin="+claims.get("admin"));
    }
}

jwt 实现登录功能

token工具类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@ConfigurationProperties("jwt.config")
public class JwtUtil {

    private String key ;

    private long ttl ;//一个小时

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public long getTtl() {
        return ttl;
    }

    public void setTtl(long ttl) {
        this.ttl = ttl;
    }

    /**
     * 生成JWT
     *
     * @param id
     * @param subject
     * @return
     */
    public String createJWT(String id, String subject, String roles) {
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        JwtBuilder builder = Jwts.builder().setId(id)
                .setSubject(subject)
                .setIssuedAt(now)
                .signWith(SignatureAlgorithm.HS256, key).claim("roles", roles);
        if (ttl > 0) {
            builder.setExpiration( new Date( nowMillis + ttl));
        }
        return builder.compact();
    }

    /**
     * 解析JWT
     * @param jwtStr
     * @return
     */
    public Claims parseJWT(String jwtStr){
        return  Jwts.parser()
                .setSigningKey(key)
                .parseClaimsJws(jwtStr)
                .getBody();
    }

}

设置过期时间

1
2
3
4
jwt:
  config:
    key: java1995  #jwt
    ttl: 60000  #一分钟

初始化JWT

1
2
3
4
 @Bean
    public JwtUtil jwtUtil(){
        return new JwtUtil();
    }

Controller

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
	@Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private HttpServletRequest request;
	
	 @RequestMapping(value = "/login",method = RequestMethod.POST)
    public Result login(@RequestBody Map<String,String> map){

       Admin admin=   adminService.login(map.get("loginname"),map.get("password"));

       //有这个用户,并且用户的密码正确匹配
       if (admin!=null&&bCryptPasswordEncoder.matches(map.get("password"),admin.getPassword())){
	   //登录成功时创建tOken
           String token = jwtUtil.createJWT(admin.getId(), admin.getLoginname(), "admin");
           Map<String,Object> jwtMap=new HashMap<String,Object>();

           jwtMap.put("token",token);
           jwtMap.put("loginname",admin.getLoginname());
           return  new Result(true,StatusCode.OK,"登录成功",jwtMap);

       }else {

        return  new Result(false,StatusCode.ERROR,"登录失败");
       }

    }

@RequestMapping(value = "/{adminId}",method = RequestMethod.DELETE)
    public Result deleteUserById(@PathVariable String adminId){

			//从请求头中获得token
        String authHeader = request.getHeader("Authorization");

        if (authHeader==null){

        return new Result(false, StatusCode.ACCESS_ERROR,"权限不足");
        }
        if (!authHeader.startsWith("Bearer ")){

            return new Result(false, StatusCode.ACCESS_ERROR,"权限不足");
        }
        String token=authHeader.substring(7);//提取token
        Claims claims = jwtUtil.parseJWT(token);
        if (claims==null){

            return new Result(false, StatusCode.ACCESS_ERROR,"权限不足");
        }
        if (!"admin".equals(claims.get("roles"))){

            return new Result(false, StatusCode.ACCESS_ERROR,"权限不足");
        }
        adminService.deleteUserById(adminId);

        return new Result(true, StatusCode.OK,"删除用户成功");
    }

使用拦截器方式实现 Token 鉴权

如果我们每个方法都去写一段代码,冗余度太高,不利于维护,那如何做使我们 的代码看起来更清爽呢?我们可以将这段代码放入拦截器去实现。

添加拦截器

Spring 为我们提供了 org.springframework.web.servlet.handler.HandlerInterceptorAdapter 这个适配器, 继承此类,可以非常方便的实现自己的拦截器。
他有三个方法:分别实现预处理、后处理(调用了 Service 并返回 ModelAndView,但未进行页面渲染)、返回处理(已经渲染了页面)
在 preHandle 中,可以进行编码、安全控制等处理;
在 postHandle 中,有机会修改 ModelAndView;
在 afterCompletion 中,可以根据 ex 是否为 null 判断是否发生了异常,进行日志记录。

过滤器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Component
public class JwtFilter extends HandlerInterceptorAdapter {
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws
Exception {
System.out.println("经过了拦截器");
final String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
final String token = authHeader.substring(7); // The part after "Bearer
"
Claims claims = jwtUtil.parseJWT(token);
if (claims != null) {
if("admin".equals(claims.get("roles"))){//如果是管理员
request.setAttribute("admin_claims", claims);
}
if("user".equals(claims.get("roles"))){//如果是用户
request.setAttribute("user_claims", claims);
}
}
}
return true;
}
}
1

拦截器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17

/**
 * 添加拦截器到环境中
 */
@Configuration
public class ApplicationConfig extends WebMvcConfigurationSupport {

    @Autowired
    private JwtFilter jwtFilter;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtFilter)
                .addPathPatterns("/**")
                .excludePathPatterns("/**/login");
    }
}

删除

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 删除
* @param id
*/
@RequestMapping(value="/{id}",method= RequestMethod.DELETE)
public Result delete(@PathVariable String id ){
Claims claims=(Claims) request.getAttribute("admin_claims");
if(claims==null){
return new Result(true,StatusCode.ACCESSRROR,"无权访问");
}
userService.deleteById(id);
return new Result(true,StatusCode.OK,"删除成功");
}



//登录

@RequestMapping(value = "/login",method = RequestMethod.POST)
    public Result login(@RequestBody Map<String,String> map){

        User user=   userService.login(map.get("mobile"),map.get("password"));

        //有这个用户,并且用户的密码正确匹配
        if (user!=null&&bCryptPasswordEncoder.matches(map.get("password"),user.getPassword())){
            String token = jwtUtil.createJWT(user.getId(),user.getNickname(), "user");
            Map<String,Object> jwtMap=new HashMap<String,Object>();

            jwtMap.put("token",token);
            jwtMap.put("nickName",user.getNickname());
            return  new Result(true,StatusCode.OK,"登录成功",jwtMap);

        }else {

            return  new Result(false,StatusCode.ERROR,"登录失败");
        }

    }