好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

SpringBoot+Spring Security+JWT实现RESTful Api权限控制的方法

摘要:用spring-boot开发restful api非常的方便,在生产环境中,对发布的api增加授权保护是非常必要的。现在我们来看如何利用jwt技术为api增加授权保护,保证只有获得授权的用户才能够访问api。

一:开发一个简单的api

在idea开发工具中新建一个maven工程,添加对应的依赖如下:

?

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

<dependency>

    <groupid>org.springframework.boot</groupid>

    <artifactid>spring-boot-starter</artifactid>

   </dependency>

 

   <dependency>

    <groupid>org.springframework.boot</groupid>

    <artifactid>spring-boot-starter-test</artifactid>

    <scope>test</scope>

   </dependency>

 

   <dependency>

    <groupid>org.springframework.boot</groupid>

    <artifactid>spring-boot-starter-web</artifactid>

   </dependency>

 

   <!-- spring-data-jpa -->

   <dependency>

    <groupid>org.springframework.boot</groupid>

    <artifactid>spring-boot-starter-data-jpa</artifactid>

   </dependency>

 

   <!-- mysql -->

   <dependency>

    <groupid>mysql</groupid>

    <artifactid>mysql-connector-java</artifactid>

    <version> 5.1 . 30 </version>

   </dependency>

 

   <!-- spring-security 和 jwt -->

   <dependency>

    <groupid>org.springframework.boot</groupid>

    <artifactid>spring-boot-starter-security</artifactid>

   </dependency>

   <dependency>

    <groupid>io.jsonwebtoken</groupid>

    <artifactid>jjwt</artifactid>

    <version> 0.7 . 0 </version>

   </dependency>

新建一个usercontroller.java文件,在里面在中增加一个hello方法:

?

1

2

3

4

5

@requestmapping ( "/hello" )

  @responsebody

  public string hello(){

   return "hello" ;

  }

这样一个简单的restful api就开发好了。

现在我们运行一下程序看看效果,执行jwtauthapplication.java类中的main方法:

等待程序启动完成后,可以简单的通过curl工具进行api的调用,如下图:

至此,我们的接口就开发完成了。但是这个接口没有任何授权防护,任何人都可以访问,这样是不安全的,下面我们开始加入授权机制。

二:增加用户注册功能

首先增加一个实体类user.java:

?

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

package boss.portal.entity;

 

import javax.persistence.*;

 

/**

  * @author zhaoxinguo on 2017/9/13.

  */

@entity

@table (name = "tb_user" )

public class user {

 

  @id

  @generatedvalue

  private long id;

  private string username;

  private string password;

 

  public long getid() {

   return id;

  }

 

  public string getusername() {

   return username;

  }

 

  public void setusername(string username) {

   this .username = username;

  }

 

  public string getpassword() {

   return password;

  }

 

  public void setpassword(string password) {

   this .password = password;

  }

}

然后增加一个repository类userrepository,可以读取和保存用户信息:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

package boss.portal.repository;

 

import boss.portal.entity.user;

import org.springframework.data.jpa.repository.jparepository;

 

/**

  * @author zhaoxinguo on 2017/9/13.

  */

public interface userrepository extends jparepository<user, long > {

 

  user findbyusername(string username);

 

}

在usercontroller类中增加注册方法,实现用户注册的接口:

?

1

2

3

4

5

6

7

8

9

/**

   * 该方法是注册用户的方法,默认放开访问控制

   * @param user

   */

  @postmapping ( "/signup" )

  public void signup( @requestbody user user) {

   user.setpassword(bcryptpasswordencoder.encode(user.getpassword()));

   applicationuserrepository.save(user);

  }

其中的@postmapping("/signup")

这个方法定义了用户注册接口,并且指定了url地址是/users/signup。由于类上加了注解 @requestmapping([/users]),类中的所有方法的url地址都会有/users前缀,所以在方法上只需指定/signup子路径即可。

密码采用了bcryptpasswordencoder进行加密,我们在application中增加bcryptpasswordencoder实例的定义。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

package boss.portal;

 

import org.springframework.boot.springapplication;

import org.springframework.boot.autoconfigure.springbootapplication;

import org.springframework.context.annotation.bean;

import org.springframework.security.crypto.bcrypt.bcryptpasswordencoder;

 

@springbootapplication

public class jwtauthapplication {

 

     @bean

     public bcryptpasswordencoder bcryptpasswordencoder() {

         return new bcryptpasswordencoder();

     }

 

     public static void main(string[] args) {

         springapplication.run(jwtauthapplication. class , args);

     }

}

三:增加jwt认证功能

用户填入用户名密码后,与数据库里存储的用户信息进行比对,如果通过,则认证成功。传统的方法是在认证通过后,创建sesstion,并给客户端返回cookie。现在我们采用jwt来处理用户名密码的认证。区别在于,认证通过后,服务器生成一个token,将token返回给客户端,客户端以后的所有请求都需要在http头中指定该token。服务器接收的请求后,会对token的合法性进行验证。验证的内容包括:

内容是一个正确的jwt格式 检查签名 检查claims 检查权限

处理登录

创建一个类jwtloginfilter,核心功能是在验证用户名密码正确后,生成一个token,并将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

57

58

59

60

61

62

63

64

65

66

67

68

package boss.portal.web.filter;

import boss.portal.entity.user;

import com.fasterxml.jackson.databind.objectmapper;

import io.jsonwebtoken.jwts;

import io.jsonwebtoken.signaturealgorithm;

import org.springframework.security.authentication.authenticationmanager;

import org.springframework.security.authentication.usernamepasswordauthenticationtoken;

import org.springframework.security.core.authentication;

import org.springframework.security.core.authenticationexception;

import org.springframework.security.web.authentication.usernamepasswordauthenticationfilter;

 

import javax.servlet.filterchain;

import javax.servlet.servletexception;

import javax.servlet.http.httpservletrequest;

import javax.servlet.http.httpservletresponse;

import java.io.ioexception;

import java.util.arraylist;

import java.util.date;

 

/**

  * 验证用户名密码正确后,生成一个token,并将token返回给客户端

  * 该类继承自usernamepasswordauthenticationfilter,重写了其中的2个方法

  * attemptauthentication :接收并解析用户凭证。

  * successfulauthentication :用户成功登录后,这个方法会被调用,我们在这个方法里生成token。

  * @author zhaoxinguo on 2017/9/12.

  */

public class jwtloginfilter extends usernamepasswordauthenticationfilter {

 

  private authenticationmanager authenticationmanager;

 

  public jwtloginfilter(authenticationmanager authenticationmanager) {

   this .authenticationmanager = authenticationmanager;

  }

 

  // 接收并解析用户凭证

  @override

  public authentication attemptauthentication(httpservletrequest req,

             httpservletresponse res) throws authenticationexception {

   try {

    user user = new objectmapper()

      .readvalue(req.getinputstream(), user. class );

 

    return authenticationmanager.authenticate(

      new usernamepasswordauthenticationtoken(

        user.getusername(),

        user.getpassword(),

        new arraylist<>())

    );

   } catch (ioexception e) {

    throw new runtimeexception(e);

   }

  }

 

  // 用户成功登录后,这个方法会被调用,我们在这个方法里生成token

  @override

  protected void successfulauthentication(httpservletrequest req,

            httpservletresponse res,

            filterchain chain,

            authentication auth) throws ioexception, servletexception {

 

   string token = jwts.builder()

     .setsubject(((org.springframework.security.core.userdetails.user) auth.getprincipal()).getusername())

     .setexpiration( new date(system.currenttimemillis() + 60 * 60 * 24 * 1000 ))

     .signwith(signaturealgorithm.hs512, "myjwtsecret" )

     测试数据pact();

   res.addheader( "authorization" , "bearer " + token);

  }

}

该类继承自usernamepasswordauthenticationfilter,重写了其中的2个方法:

attemptauthentication  :接收并解析用户凭证。

successfulauthentication  :用户成功登录后,这个方法会被调用,我们在这个方法里生成token。

授权验证

用户一旦登录成功后,会拿到token,后续的请求都会带着这个token,服务端会验证token的合法性。

创建jwtauthenticationfilter类,我们在这个类中实现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

57

58

59

60

61

package boss.portal.web.filter;

import io.jsonwebtoken.jwts;

import org.springframework.security.authentication.authenticationmanager;

import org.springframework.security.authentication.usernamepasswordauthenticationtoken;

import org.springframework.security.core.context.securitycontextholder;

import org.springframework.security.web.authentication.HdhCmsTestbasicauthenticationfilter;

 

import javax.servlet.filterchain;

import javax.servlet.servletexception;

import javax.servlet.http.httpservletrequest;

import javax.servlet.http.httpservletresponse;

import java.io.ioexception;

import java.util.arraylist;

 

/**

  * token的校验

  * 该类继承自basicauthenticationfilter,在dofilterinternal方法中,

  * 从http头的authorization 项读取token数据,然后用jwts包提供的方法校验token的合法性。

  * 如果校验通过,就认为这是一个取得授权的合法请求

  * @author zhaoxinguo on 2017/9/13.

  */

public class jwtauthenticationfilter extends basicauthenticationfilter {

 

  public jwtauthenticationfilter(authenticationmanager authenticationmanager) {

   super (authenticationmanager);

  }

 

  @override

  protected void dofilterinternal(httpservletrequest request, httpservletresponse response, filterchain chain) throws ioexception, servletexception {

   string header = request.getheader( "authorization" );

 

   if (header == null || !header.startswith( "bearer " )) {

    chain.dofilter(request, response);

    return ;

   }

 

   usernamepasswordauthenticationtoken authentication = getauthentication(request);

 

   securitycontextholder.getcontext().setauthentication(authentication);

   chain.dofilter(request, response);

 

  }

 

  private usernamepasswordauthenticationtoken getauthentication(httpservletrequest request) {

   string token = request.getheader( "authorization" );

   if (token != null ) {

    // parse the token.

    string user = jwts.parser()

      .setsigningkey( "myjwtsecret" )

      .parseclaimsjws(token.replace( "bearer " , "" ))

      .getbody()

      .getsubject();

 

    if (user != null ) {

     return new usernamepasswordauthenticationtoken(user, null , new arraylist<>());

    }

    return null ;

   }

   return null ;

  }

}

该类继承自basicauthenticationfilter,在dofilterinternal方法中,从http头的 authorization  项读取token数据,然后用jwts包提供的方法校验token的合法性。如果校验通过,就认为这是一个取得授权的合法请求。

springsecurity配置

通过springsecurity的配置,将上面的方法组合在一起。

?

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

package boss.portal.security;

import boss.portal.web.filter.jwtloginfilter;

import boss.portal.web.filter.jwtauthenticationfilter;

import org.springframework.boot.autoconfigure.security.securityproperties;

import org.springframework.context.annotation.configuration;

import org.springframework.core.annotation.order;

import org.springframework.http.httpmethod;

import org.springframework.security.config.annotation.authentication.builders.authenticationmanagerbuilder;

import org.springframework.security.config.annotation.web.builders.httpsecurity;

import org.springframework.security.config.annotation.web.configuration.websecurityconfigureradapter;

import org.springframework.security.core.userdetails.userdetailsservice;

import org.springframework.security.crypto.bcrypt.bcryptpasswordencoder;

 

/**

  * springsecurity的配置

  * 通过springsecurity的配置,将jwtloginfilter,jwtauthenticationfilter组合在一起

  * @author zhaoxinguo on 2017/9/13.

  */

@configuration

@order (securityproperties.access_override_order)

public class websecurityconfig extends websecurityconfigureradapter {

 

  private userdetailsservice userdetailsservice;

 

  private bcryptpasswordencoder bcryptpasswordencoder;

 

  public websecurityconfig(userdetailsservice userdetailsservice, bcryptpasswordencoder bcryptpasswordencoder) {

   this .userdetailsservice = userdetailsservice;

   this .bcryptpasswordencoder = bcryptpasswordencoder;

  }

 

  @override

  protected void configure(httpsecurity http) throws exception {

   http.cors().and().csrf().disable().authorizerequests()

     .antmatchers(httpmethod.post, "/users/signup" ).permitall()

     .anyrequest().authenticated()

     .and()

     .addfilter( new jwtloginfilter(authenticationmanager()))

     .addfilter( new jwtauthenticationfilter(authenticationmanager()));

  }

 

  @override

  public void configure(authenticationmanagerbuilder auth) throws exception {

   auth.userdetailsservice(userdetailsservice).passwordencoder(bcryptpasswordencoder);

  }

 

}

这是标准的springsecurity配置内容,就不在详细说明。注意其中的

?

1

2

.addfilter( new jwtloginfilter(authenticationmanager()))

.addfilter( new jwtauthenticationfilter(authenticationmanager()))

这两行,将我们定义的jwt方法加入springsecurity的处理流程中。

下面对我们的程序进行简单的验证:

# 请求hello接口,会收到403错误,如下图:

curl http://localhost:8080/hello

# 注册一个新用户curl -h"content-type: application/json" -x post -d '{"username":"admin","password":"password"}' http://localhost:8080/users/signup

如下图:

# 登录,会返回token,在http header中,authorization: bearer 后面的部分就是tokencurl -i -h"content-type: application/json" -x post -d '{"username":"admin","password":"password"}' http://localhost:8080/login

如下图:

# 用登录成功后拿到的token再次请求hello接口# 将请求中的xxxxxx替换成拿到的token# 这次可以成功调用接口了curl -h"content-type: application/json" \-h"authorization: bearer xxxxxx" \"http://localhost:8080/users/hello"

如下图:

五:总结

至此,给springboot的接口加上jwt认证的功能就实现了,过程并不复杂,主要是开发两个springsecurity的filter,来生成和校验jwt token。

jwt作为一个无状态的授权校验技术,非常适合于分布式系统架构,因为服务端不需要保存用户状态,因此就无需采用redis等技术,在各个服务节点之间共享session数据。

六:源码下载地址

地址: https://gitee测试数据/micai/springboot-springsecurity-jwt-demo

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

原文链接:https://blog.csdn.net/sxdtzhaoxinguo/article/details/77965226

查看更多关于SpringBoot+Spring Security+JWT实现RESTful Api权限控制的方法的详细内容...

  阅读:12次