当前位置:   article > 正文

SpringBoot+SpringSecurity+JWT实RESTfulAPI权限控制_springboot security jwt

springboot security jwt

关于什么是jwt(json web token),还有jwt的工作流程我这边就不多介绍,主要给大家介绍一下SpringBoot中如何整合Security然后在添加jwt的支持。

​​​​​​​史上最全Java技术栈面试题都在这里面了:Java面试题

通过SpringBoot+Security+JWT来实现token校验的过程。在生产环境中,对发布API增加授权保护是非常必要的一件事情,这里我们就是利用jwt生成token,利用token来进行接口安全验证。

在整合jwt之前,我们首先要在SpringBoot中整合security的模块,来实现基于security的授权控制。用过security的人都知道,它的功能无比的强大比shiro还要强大,但是今天我就介绍security中关于权限控制和是登录验证的这一块的功能。

1.在开始之前我们先在pom.xml中添加security和jwt的相关依赖,如下所示:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <groupId>org.sang</groupId>
  6. <artifactId>test26-security</artifactId>
  7. <version>0.0.1-SNAPSHOT</version>
  8. <packaging>jar</packaging>
  9. <name>Test26-Security</name>
  10. <description>Demo project for Spring Boot</description>
  11. <parent>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-parent</artifactId>
  14. <version>1.4.3.RELEASE</version>
  15. <relativePath/> <!-- lookup parent from repository -->
  16. </parent>
  17. <properties>
  18. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  19. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  20. <java.version>1.8</java.version>
  21. </properties>
  22. <dependencies>
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-starter-data-jpa</artifactId>
  26. </dependency>
  27. <dependency>
  28. <groupId>org.springframework.boot</groupId>
  29. <artifactId>spring-boot-starter-security</artifactId>
  30. </dependency>
  31. <dependency>
  32. <groupId>org.springframework.boot</groupId>
  33. <artifactId>spring-boot-starter-thymeleaf</artifactId>
  34. </dependency>
  35. <dependency>
  36. <groupId>org.springframework.boot</groupId>
  37. <artifactId>spring-boot-starter-test</artifactId>
  38. <scope>test</scope>
  39. </dependency>
  40. <dependency>
  41. <groupId>org.thymeleaf.extras</groupId>
  42. <artifactId>thymeleaf-extras-springsecurity4</artifactId>
  43. </dependency>
  44. <dependency>
  45. <groupId>mysql</groupId>
  46. <artifactId>mysql-connector-java</artifactId>
  47. <version>5.1.40</version>
  48. </dependency>
  49. <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
  50. <dependency>
  51. <groupId>io.jsonwebtoken</groupId>
  52. <artifactId>jjwt</artifactId>
  53. <version>0.9.0</version>
  54. </dependency>
  55. </dependencies>
  56. <build>
  57. <plugins>
  58. <plugin>
  59. <groupId>org.springframework.boot</groupId>
  60. <artifactId>spring-boot-maven-plugin</artifactId>
  61. </plugin>
  62. </plugins>
  63. </build>
  64. </project>

2.在application.properties中添加数据库和jpa还有jwt的相关配置。具体如下所示:

  1. spring.datasource.driver-class-name=com.mysql.jdbc.Driver
  2. spring.datasource.url=jdbc:mysql://localhost:3306/security_db?useUnicode=true&characterEncoding=utf-8
  3. spring.datasource.username=root
  4. spring.datasource.password=220316
  5. logging.level.org.springframework.security=info
  6. spring.thymeleaf.cache=false
  7. spring.jpa.hibernate.ddl-auto=update
  8. spring.jpa.show-sql=true
  9. spring.jackson.serialization.indent_output=true
  10. # JWT 604800
  11. jwt.header:Authorization
  12. jwt.secret:mySecret
  13. jwt.expiration:10
  14. jwt.tokenHead:Bearer
  15. jwt.route.authentication.path:auth/login
  16. jwt.route.authentication.refresh:auth/refresh
  17. jwt.route.authentication.register:"auth/register"

3.新建实体类,进行用户登录的授权验证。(这里我新建两个实体类,一个是用户实体类,一个是用户关系实体类)

用户实体类:SysUser

  1. package org.security.entity;
  2. import org.springframework.security.core.GrantedAuthority;
  3. import org.springframework.security.core.authority.SimpleGrantedAuthority;
  4. import org.springframework.security.core.userdetails.UserDetails;
  5. import javax.persistence.*;
  6. import java.util.ArrayList;
  7. import java.util.Collection;
  8. import java.util.Date;
  9. import java.util.List;
  10. @Entity
  11. public class SysUser implements UserDetails {
  12. @Id
  13. @GeneratedValue
  14. private Long id;
  15. private String username;
  16. private String password;
  17. private Date lastPasswordResetDate;
  18. @ManyToMany(cascade = {CascadeType.REFRESH},fetch = FetchType.EAGER)
  19. private List<SysRole> roles;
  20. public Long getId() {
  21. return id;
  22. }
  23. public void setId(Long id) {
  24. this.id = id;
  25. }
  26. public void setUsername(String username) {
  27. this.username = username;
  28. }
  29. public void setPassword(String password) {
  30. this.password = password;
  31. }
  32. public List<SysRole> getRoles() {
  33. return roles;
  34. }
  35. public void setRoles(List<SysRole> roles) {
  36. this.roles = roles;
  37. }
  38. public Date getLastPasswordResetDate() {
  39. return lastPasswordResetDate;
  40. }
  41. public void setLastPasswordResetDate(Date lastPasswordResetDate) {
  42. this.lastPasswordResetDate = lastPasswordResetDate;
  43. }
  44. @Override
  45. public Collection<? extends GrantedAuthority> getAuthorities() {
  46. List<GrantedAuthority> auths = new ArrayList<>();
  47. List<SysRole> roles = this.getRoles();
  48. for (SysRole role : roles) {
  49. auths.add(new SimpleGrantedAuthority(role.getName()));
  50. }
  51. return auths;
  52. }
  53. @Override
  54. public String getPassword() {
  55. return this.password;
  56. }
  57. @Override
  58. public String getUsername() {
  59. return this.username;
  60. }
  61. @Override
  62. public boolean isAccountNonExpired() {
  63. return true;
  64. }
  65. @Override
  66. public boolean isAccountNonLocked() {
  67. return true;
  68. }
  69. @Override
  70. public boolean isCredentialsNonExpired() {
  71. return true;
  72. }
  73. @Override
  74. public boolean isEnabled() {
  75. return true;
  76. }
  77. }

因为SysUser实体类继承UserDetails接口,这个类是基于Security的安全服务。所以通过继承UserDetails这个类,我们就可以实现Security中相关的功能了,后面我们会具体介绍是怎么一回事。

用户关系实体类:SysRole

  1. package org.security.entity;
  2. import javax.persistence.Entity;
  3. import javax.persistence.GeneratedValue;
  4. import javax.persistence.Id;
  5. @Entity
  6. public class SysRole {
  7. @Id
  8. @GeneratedValue
  9. private Long id;
  10. private String name;
  11. public Long getId() {
  12. return id;
  13. }
  14. public void setId(Long id) {
  15. this.id = id;
  16. }
  17. public String getName() {
  18. return name;
  19. }
  20. public void setName(String name) {
  21. this.name = name;
  22. }
  23. }

SysRole实体类是保存着用户的拥有什么样权限的实体类。和用户实体是多对一的关系,也就是一个用户可以拥有多个权限。

实体类创建好之后,我们就可以编写相应的dao,service,controller类来实现用户登录的功能了。这里我会新建一个login.html页面和index.html页面,来进行登录校验的和权限控制演示。

dao接口(这里我采用jpa来实现用户查询的功能)

  1. package org.security.dao;
  2. import org.security.entity.SysUser;
  3. import org.springframework.data.jpa.repository.JpaRepository;
  4. public interface SysUserRepository extends JpaRepository<SysUser, Long> {
  5. SysUser findByUsername(String username);
  6. }

service业务类

  1. package org.security.service;
  2. import org.security.dao.SysUserRepository;
  3. import org.security.entity.SysUser;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.security.core.userdetails.UserDetails;
  6. import org.springframework.security.core.userdetails.UserDetailsService;
  7. import org.springframework.security.core.userdetails.UsernameNotFoundException;
  8. public class CustomUserService implements UserDetailsService {
  9. @Autowired
  10. SysUserRepository userRepository;
  11. @Override
  12. public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
  13. SysUser user = userRepository.findByUsername(s);
  14. if (user == null) {
  15. throw new UsernameNotFoundException("用户名不存在");
  16. }
  17. System.out.println("s:"+s);
  18. System.out.println("username:"+user.getUsername()+";password:"+user.getPassword());
  19. System.out.println("size:"+user.getRoles().size());
  20. System.out.println("role:"+user.getRoles().get(0).getName());
  21. return user;
  22. }
  23. }

controller控制类:

  1. package org.security.controller;
  2. import org.security.entity.Msg;
  3. import org.springframework.security.access.prepost.PreAuthorize;
  4. import org.springframework.stereotype.Controller;
  5. import org.springframework.ui.Model;
  6. import org.springframework.web.bind.annotation.RequestMapping;
  7. import org.springframework.web.bind.annotation.RequestMethod;
  8. import org.springframework.web.bind.annotation.ResponseBody;
  9. /**
  10. * Created by linzhiqiang on 2017/10/26
  11. */
  12. @Controller
  13. public class HomeController {
  14. @RequestMapping("/")
  15. public String index(Model model) {
  16. Msg msg = new Msg("测试标题", "测试内容", "额外信息,只对管理员显示");
  17. model.addAttribute("msg", msg);
  18. return "index";
  19. }
  20. @PreAuthorize("hasAuthority('ROLE_USER')")
  21. @RequestMapping(value="/admin/test1")
  22. @ResponseBody
  23. public String adminTest1() {
  24. return "ROLE_USER";
  25. }
  26. @PreAuthorize("hasAuthority('ROLE_ADMIN')")
  27. @RequestMapping("/admin/test2")
  28. @ResponseBody
  29. public String adminTest2() {
  30. return "ROLE_ADMIN";
  31. }
  32. }

login.html页面:

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <meta charset="UTF-8"/>
  5. <title>登录</title>
  6. <link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
  7. <link rel="stylesheet" th:href="@{css/signin.css}"/>
  8. <style type="text/css">
  9. body {
  10. padding-top: 50px;
  11. }
  12. .starter-template {
  13. padding: 40px 15px;
  14. text-align: center;
  15. }
  16. </style>
  17. </head>
  18. <body>
  19. <nav class="navbar navbar-inverse navbar-fixed-top">
  20. <div class="container">
  21. <div class="navbar-header">
  22. <a class="navbar-brand" href="#">Spring Security演示</a>
  23. </div>
  24. <div id="navbar" class="collapse navbar-collapse">
  25. <ul class="nav navbar-nav">
  26. <li><a th:href="@{/}">首页</a></li>
  27. <li><a th:href="@{http://www.baidu.com}">百度</a></li>
  28. </ul>
  29. </div>
  30. </div>
  31. </nav>
  32. <div class="container">
  33. <div class="starter-template">
  34. <p th:if="${param.logout}" class="bg-warning">已注销</p>
  35. <p th:if="${param.error}" class="bg-danger">有错误,请重试</p>
  36. <h2>使用账号密码登录</h2>
  37. <form class="form-signin" role="form" name="form" th:action="@{/login}" action="/login" method="post">
  38. <div class="form-group">
  39. <label for="username">账号</label>
  40. <input type="text" class="form-control" name="username" value="" placeholder="账号"/>
  41. </div>
  42. <div class="form-group">
  43. <label for="password">密码</label>
  44. <input type="password" class="form-control" name="password" placeholder="密码"/>
  45. </div>
  46. <input type="submit" id="login" value="Login" class="btn btn-primary"/>
  47. </form>
  48. </div>
  49. </div>
  50. </body>
  51. </html>

index.html页面:

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org"
  3. xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
  4. <head>
  5. <meta charset="UTF-8"/>
  6. <title sec:authentication="name"></title>
  7. <link rel="stylesheet" th:href="@{css/bootstrap.min.css}"/>
  8. <style type="text/css">
  9. body {
  10. padding-top: 50px;
  11. }
  12. .starter-template {
  13. padding: 40px 15px;
  14. text-align: center;
  15. }
  16. </style>
  17. </head>
  18. <body>
  19. <nav class="navbar navbar-inverse navbar-fixed-top">
  20. <div class="container">
  21. <div class="navbar-header">
  22. <a class="navbar-brand" href="#">Spring Security演示</a>
  23. </div>
  24. <div id="navbar" class="collapse navbar-collapse">
  25. <ul class="nav navbar-nav">
  26. <li><a th:href="@{/}">首页</a></li>
  27. <li><a th:href="@{http://www.baidu.com}">百度</a></li>
  28. </ul>
  29. </div>
  30. </div>
  31. </nav>
  32. <div class="container">
  33. <div class="starter-template">
  34. <h1 th:text="${msg.title}"></h1>
  35. <p class="bg-primary" th:text="${msg.content}"></p>
  36. <div sec:authorize="hasRole('ROLE_ADMIN')">
  37. <p class="bg-info" th:text="${msg.extraInfo}"></p>
  38. </div>
  39. <div sec:authorize="hasRole('ROLE_USER')">
  40. <p class="bg-info">无更多显示信息</p>
  41. </div>
  42. <form th:action="@{/logout}" method="post">
  43. <input type="submit" class="btn btn-primary" value="注销"/>
  44. </form>
  45. </div>
  46. </div>
  47. </body>
  48. </html>

4.SpringBoot中配置基于security的支持,这里我有两个配置

一个是没有登录用户都跳转到login.html页面的配置:

  1. package org.security.config;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
  4. import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
  5. @Configuration
  6. public class WebMvcConfig extends WebMvcConfigurerAdapter {
  7. @Override
  8. public void addViewControllers(ViewControllerRegistry registry) {
  9. registry.addViewController("/login").setViewName("login");
  10. }
  11. }

接下来就是security核心的配置类了:

  1. package org.security.config;
  2. import org.security.service.CustomUserService;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.http.HttpMethod;
  7. import org.springframework.security.authentication.AuthenticationManager;
  8. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  9. import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
  10. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  11. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
  12. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  13. import org.springframework.security.config.http.SessionCreationPolicy;
  14. import org.springframework.security.core.userdetails.UserDetailsService;
  15. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  16. import org.springframework.security.crypto.password.PasswordEncoder;
  17. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  18. @Configuration
  19. @EnableWebSecurity
  20. @EnableGlobalMethodSecurity(prePostEnabled = true)
  21. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  22. @Bean
  23. protected UserDetailsService customUserService() {
  24. return new CustomUserService();
  25. }
  26. @Autowired
  27. public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
  28. authenticationManagerBuilder
  29. // 设置UserDetailsService
  30. .userDetailsService(customUserService())
  31. // 使用BCrypt进行密码的hash
  32. .passwordEncoder(passwordEncoder());
  33. }
  34. // 装载BCrypt密码编码器
  35. @Bean
  36. public PasswordEncoder passwordEncoder() {
  37. return new BCryptPasswordEncoder();
  38. }
  39. @Bean
  40. public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
  41. return new JwtAuthenticationTokenFilter();
  42. }
  43. @Override
  44. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  45. auth.userDetailsService(customUserService());
  46. }
  47. @Bean
  48. @Override
  49. public AuthenticationManager authenticationManagerBean() throws Exception {
  50. return super.authenticationManagerBean();
  51. }
  52. @Override
  53. protected void configure(HttpSecurity httpSecurity) throws Exception {
  54. httpSecurity
  55. // 由于使用的是JWT,我们这里不需要csrf
  56. // .csrf().disable()
  57. // 基于token,所以不需要session
  58. // .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
  59. .authorizeRequests()
  60. //.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
  61. // 允许对于网站静态资源的无授权访问
  62. .antMatchers(
  63. HttpMethod.GET,
  64. "/",
  65. "/*.html",
  66. "/favicon.ico",
  67. "/**/*.html",
  68. "/**/*.css",
  69. "/**/*.js"
  70. ).permitAll()
  71. // 对于获取token的rest api要允许匿名访问
  72. .antMatchers("/auth/**").permitAll()
  73. .antMatchers("/admin/**").hasIpAddress("127.0.0.1")
  74. .antMatchers("/admin/**").access("hasAuthority('ROLE_ADMIN')")
  75. .anyRequest().authenticated().and().formLogin().loginPage("/login")
  76. .failureUrl("/login?error").permitAll().and().logout().permitAll();
  77. // 除上面外的所有请求全部需要鉴权认证
  78. // .anyRequest().authenticated();
  79. // 添加JWT filter
  80. // httpSecurity
  81. // .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
  82. // 禁用缓存
  83. // httpSecurity.headers().cacheControl();
  84. }
  85. }

这里我暂时先把jwt相关的代码先注释掉,将注解取消掉的话,就可以实现jwt相关的功能了。

5.打开浏览器进行功能测试,下面是测试的结果:



如果未登录的话,就都会跳转到这个login.html登录页面


登录成功后进入的index页面,这里测试的账户权限类型是admin,下面有两个地址一个是admin/test1只能user权限才可以访问,还有一个是admin/test2拥有admin权限的人才可以访问,下面我们开始测试。

因为权限不够所以,返回的是403错误。

权限够的话,返回的是这个接口获取的数据。

到这里关于SpringBoot整合Security就介绍完了,接下来就是如何添加jwt的支持了。

6.基本过程和security差不多:一:首先创建一个工具类,用来生成jwt的token和token的验证刷新之类的。

二:然后创建一个fiter类,并配置注入security配置文件中

三:接下来就是创建dao,service,controller来实现接口。

四:最后就是利用postman来测试是否实现jwt功能

工具类JwtTokenUtil:

  1. package org.security.util;
  2. import io.jsonwebtoken.Claims;
  3. import io.jsonwebtoken.Jwts;
  4. import io.jsonwebtoken.SignatureAlgorithm;
  5. import org.security.entity.SysUser;
  6. import org.springframework.beans.factory.annotation.Value;
  7. import org.springframework.security.core.userdetails.UserDetails;
  8. import org.springframework.stereotype.Component;
  9. import java.io.Serializable;
  10. import java.util.Date;
  11. import java.util.HashMap;
  12. import java.util.Map;
  13. @Component
  14. public class JwtTokenUtil implements Serializable {
  15. private static final long serialVersionUID = -3301605591108950415L;
  16. private static final String CLAIM_KEY_USERNAME = "sub";
  17. private static final String CLAIM_KEY_CREATED = "created";
  18. @Value("${jwt.secret}")
  19. private String secret;
  20. @Value("${jwt.expiration}")
  21. private Long expiration;
  22. public String getUsernameFromToken(String token) {
  23. String username;
  24. try {
  25. final Claims claims = getClaimsFromToken(token);
  26. username = claims.getSubject();
  27. } catch (Exception e) {
  28. username = null;
  29. }
  30. return username;
  31. }
  32. public Date getCreatedDateFromToken(String token) {
  33. Date created;
  34. try {
  35. final Claims claims = getClaimsFromToken(token);
  36. created = new Date((Long) claims.get(CLAIM_KEY_CREATED));
  37. } catch (Exception e) {
  38. created = null;
  39. }
  40. return created;
  41. }
  42. public Date getExpirationDateFromToken(String token) {
  43. Date expiration;
  44. try {
  45. final Claims claims = getClaimsFromToken(token);
  46. expiration = claims.getExpiration();
  47. } catch (Exception e) {
  48. expiration = null;
  49. }
  50. return expiration;
  51. }
  52. private Claims getClaimsFromToken(String token) {
  53. Claims claims;
  54. try {
  55. claims = Jwts.parser()
  56. .setSigningKey(secret)
  57. .parseClaimsJws(token)
  58. .getBody();
  59. } catch (Exception e) {
  60. claims = null;
  61. }
  62. return claims;
  63. }
  64. private Date generateExpirationDate() {
  65. return new Date(System.currentTimeMillis() + expiration * 1000);
  66. }
  67. private Boolean isTokenExpired(String token) {
  68. final Date expiration = getExpirationDateFromToken(token);
  69. return expiration.before(new Date());
  70. }
  71. private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
  72. return (lastPasswordReset != null && created.before(lastPasswordReset));
  73. }
  74. public String generateToken(UserDetails userDetails) {
  75. Map<String, Object> claims = new HashMap<>();
  76. claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
  77. claims.put(CLAIM_KEY_CREATED, new Date());
  78. return generateToken(claims);
  79. }
  80. String generateToken(Map<String, Object> claims) {
  81. return Jwts.builder()
  82. .setClaims(claims)
  83. .setExpiration(generateExpirationDate())
  84. .signWith(SignatureAlgorithm.HS512, secret)
  85. .compact();
  86. }
  87. public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
  88. final Date created = getCreatedDateFromToken(token);
  89. return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
  90. && !isTokenExpired(token);
  91. }
  92. public String refreshToken(String token) {
  93. String refreshedToken;
  94. try {
  95. final Claims claims = getClaimsFromToken(token);
  96. claims.put(CLAIM_KEY_CREATED, new Date());
  97. refreshedToken = generateToken(claims);
  98. } catch (Exception e) {
  99. refreshedToken = null;
  100. }
  101. return refreshedToken;
  102. }
  103. public Boolean validateToken(String token, UserDetails userDetails) {
  104. SysUser user = (SysUser) userDetails;
  105. final String username = getUsernameFromToken(token);
  106. final Date created = getCreatedDateFromToken(token);
  107. // final Date expiration = getExpirationDateFromToken(token);
  108. return (
  109. username.equals(user.getUsername())
  110. && !isTokenExpired(token)
  111. && !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate()));
  112. }
  113. }

dao接口:

  1. package org.security.dao;
  2. import org.security.entity.SysUser;
  3. public interface AuthService {
  4. SysUser register(SysUser userToAdd);
  5. String login(String username, String password);
  6. String refresh(String oldToken);
  7. }

dao接口实现类(service业务类):

  1. package org.security.service;
  2. import org.security.dao.AuthService;
  3. import org.security.dao.SysUserRepository;
  4. import org.security.entity.SysUser;
  5. import org.security.util.JwtTokenUtil;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.beans.factory.annotation.Value;
  8. import org.springframework.security.authentication.AuthenticationManager;
  9. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  10. import org.springframework.security.core.Authentication;
  11. import org.springframework.security.core.context.SecurityContextHolder;
  12. import org.springframework.security.core.userdetails.UserDetails;
  13. import org.springframework.security.core.userdetails.UserDetailsService;
  14. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  15. import org.springframework.stereotype.Service;
  16. import java.util.Date;
  17. import static java.util.Arrays.asList;
  18. @Service
  19. public class AuthServiceImpl implements AuthService {
  20. private AuthenticationManager authenticationManager;
  21. private UserDetailsService userDetailsService;
  22. private JwtTokenUtil jwtTokenUtil;
  23. private SysUserRepository userRepository;
  24. @Value("${jwt.tokenHead}")
  25. private String tokenHead;
  26. @Autowired
  27. public AuthServiceImpl(
  28. AuthenticationManager authenticationManager,
  29. UserDetailsService userDetailsService,
  30. JwtTokenUtil jwtTokenUtil,
  31. SysUserRepository userRepository) {
  32. this.authenticationManager = authenticationManager;
  33. this.userDetailsService = userDetailsService;
  34. this.jwtTokenUtil = jwtTokenUtil;
  35. this.userRepository = userRepository;
  36. }
  37. @Override
  38. public SysUser register(SysUser userToAdd) {
  39. final String username = userToAdd.getUsername();
  40. if(userRepository.findByUsername(username)!=null) {
  41. return null;
  42. }
  43. BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
  44. final String rawPassword = userToAdd.getPassword();
  45. userToAdd.setPassword(encoder.encode(rawPassword));
  46. userToAdd.setLastPasswordResetDate(new Date());
  47. // userToAdd.setRoles(asList("ROLE_USER"));
  48. return userRepository.save(userToAdd);
  49. }
  50. @Override
  51. public String login(String username, String password) {
  52. UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password);
  53. // Perform the security
  54. final Authentication authentication = authenticationManager.authenticate(upToken);
  55. SecurityContextHolder.getContext().setAuthentication(authentication);
  56. // Reload password post-security so we can generate token
  57. final UserDetails userDetails = userDetailsService.loadUserByUsername(username);
  58. final String token = jwtTokenUtil.generateToken(userDetails);
  59. return token;
  60. }
  61. @Override
  62. public String refresh(String oldToken) {
  63. final String token = oldToken.substring(tokenHead.length());
  64. String username = jwtTokenUtil.getUsernameFromToken(token);
  65. SysUser user = (SysUser) userDetailsService.loadUserByUsername(username);
  66. if (jwtTokenUtil.canTokenBeRefreshed(token, user.getLastPasswordResetDate())){
  67. return jwtTokenUtil.refreshToken(token);
  68. }
  69. return null;
  70. }
  71. }

controller控制类:

  1. package org.security.controller;
  2. import org.security.dao.AuthService;
  3. import org.security.entity.SysUser;
  4. import org.security.service.JwtAuthenticationRequest;
  5. import org.security.service.JwtAuthenticationResponse;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.beans.factory.annotation.Value;
  8. import org.springframework.http.ResponseEntity;
  9. import org.springframework.security.core.AuthenticationException;
  10. import org.springframework.web.bind.annotation.RequestBody;
  11. import org.springframework.web.bind.annotation.RequestMapping;
  12. import org.springframework.web.bind.annotation.RequestMethod;
  13. import org.springframework.web.bind.annotation.RestController;
  14. import javax.servlet.http.HttpServletRequest;
  15. @RestController
  16. public class AuthController {
  17. @Value("${jwt.header}")
  18. private String tokenHeader;
  19. @Autowired
  20. private AuthService authService;
  21. @RequestMapping(value = "/auth/login", method = RequestMethod.POST)
  22. public ResponseEntity<?> createAuthenticationToken(
  23. String username,String password
  24. ) throws AuthenticationException{
  25. // @RequestBody JwtAuthenticationRequest authenticationRequest
  26. final String token = authService.login(username,password);
  27. // Return the token
  28. return ResponseEntity.ok(new JwtAuthenticationResponse(token));
  29. }
  30. @RequestMapping(value = "/auth/refresh", method = RequestMethod.GET)
  31. public ResponseEntity<?> refreshAndGetAuthenticationToken(
  32. HttpServletRequest request) throws AuthenticationException{
  33. String token = request.getHeader(tokenHeader);
  34. String refreshedToken = authService.refresh(token);
  35. if(refreshedToken == null) {
  36. return ResponseEntity.badRequest().body(null);
  37. } else {
  38. return ResponseEntity.ok(new JwtAuthenticationResponse(refreshedToken));
  39. }
  40. }
  41. @RequestMapping(value = "${jwt.route.authentication.register}", method = RequestMethod.POST)
  42. public SysUser register(@RequestBody SysUser addedUser) throws AuthenticationException{
  43. return authService.register(addedUser);
  44. }
  45. }

接在来就是最重要的filter类:

  1. package org.security.config;
  2. import java.io.IOException;
  3. import javax.servlet.FilterChain;
  4. import javax.servlet.ServletException;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. import org.security.util.JwtTokenUtil;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.beans.factory.annotation.Value;
  10. import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  11. import org.springframework.security.core.context.SecurityContextHolder;
  12. import org.springframework.security.core.userdetails.UserDetails;
  13. import org.springframework.security.core.userdetails.UserDetailsService;
  14. import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
  15. import org.springframework.stereotype.Component;
  16. import org.springframework.web.filter.OncePerRequestFilter;
  17. @Component
  18. public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
  19. @Autowired
  20. private UserDetailsService userDetailsService;
  21. @Value("${jwt.header}")
  22. private String tokenHeader;
  23. @Value("${jwt.tokenHead}")
  24. private String tokenHead;
  25. @Autowired
  26. private JwtTokenUtil jwtTokenUtil;
  27. @Override
  28. protected void doFilterInternal(
  29. HttpServletRequest request,
  30. HttpServletResponse response,
  31. FilterChain chain) throws ServletException, IOException {
  32. String authHeader = request.getHeader(this.tokenHeader);
  33. if (authHeader != null && authHeader.startsWith(tokenHead)) {
  34. final String authToken = authHeader.substring(tokenHead.length()); // The part after "Bearer "
  35. String username = jwtTokenUtil.getUsernameFromToken(authToken);
  36. logger.info("checking authentication " + username);
  37. if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
  38. UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
  39. if (jwtTokenUtil.validateToken(authToken, userDetails)) {
  40. UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
  41. userDetails, null, userDetails.getAuthorities());
  42. authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
  43. request));
  44. logger.info("authenticated user " + username + ", setting security context");
  45. SecurityContextHolder.getContext().setAuthentication(authentication);
  46. }
  47. }
  48. }
  49. chain.doFilter(request, response);
  50. }
  51. }

将security中配置文件注解去掉,去掉之后如下所示:

  1. package org.security.config;
  2. import org.security.service.CustomUserService;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.http.HttpMethod;
  7. import org.springframework.security.authentication.AuthenticationManager;
  8. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  9. import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
  10. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  11. import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
  12. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  13. import org.springframework.security.config.http.SessionCreationPolicy;
  14. import org.springframework.security.core.userdetails.UserDetailsService;
  15. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
  16. import org.springframework.security.crypto.password.PasswordEncoder;
  17. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  18. @Configuration
  19. @EnableWebSecurity
  20. @EnableGlobalMethodSecurity(prePostEnabled = true)
  21. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
  22. @Bean
  23. protected UserDetailsService customUserService() {
  24. return new CustomUserService();
  25. }
  26. @Autowired
  27. public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
  28. authenticationManagerBuilder
  29. // 设置UserDetailsService
  30. .userDetailsService(customUserService())
  31. // 使用BCrypt进行密码的hash
  32. .passwordEncoder(passwordEncoder());
  33. }
  34. // 装载BCrypt密码编码器
  35. @Bean
  36. public PasswordEncoder passwordEncoder() {
  37. return new BCryptPasswordEncoder();
  38. }
  39. @Bean
  40. public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
  41. return new JwtAuthenticationTokenFilter();
  42. }
  43. @Override
  44. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  45. auth.userDetailsService(customUserService());
  46. }
  47. @Bean
  48. @Override
  49. public AuthenticationManager authenticationManagerBean() throws Exception {
  50. return super.authenticationManagerBean();
  51. }
  52. @Override
  53. protected void configure(HttpSecurity httpSecurity) throws Exception {
  54. httpSecurity
  55. // 由于使用的是JWT,我们这里不需要csrf
  56. .csrf().disable()
  57. // 基于token,所以不需要session
  58. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
  59. .authorizeRequests()
  60. .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
  61. // 允许对于网站静态资源的无授权访问
  62. .antMatchers(
  63. HttpMethod.GET,
  64. "/",
  65. "/*.html",
  66. "/favicon.ico",
  67. "/**/*.html",
  68. "/**/*.css",
  69. "/**/*.js"
  70. ).permitAll()
  71. // 对于获取token的rest api要允许匿名访问
  72. .antMatchers("/auth/**").permitAll()
  73. .antMatchers("/admin/**").hasIpAddress("127.0.0.1")
  74. .antMatchers("/admin/**").access("hasAuthority('ROLE_ADMIN')")
  75. // .anyRequest().authenticated().and().formLogin().loginPage("/login")
  76. // .failureUrl("/login?error").permitAll().and().logout().permitAll();
  77. // 除上面外的所有请求全部需要鉴权认证
  78. .anyRequest().authenticated();
  79. // 添加JWT filter
  80. httpSecurity
  81. .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
  82. // 禁用缓存
  83. httpSecurity.headers().cacheControl();
  84. }
  85. }

到这边jwt整合也介绍了,现在我们就可以postman来进行测试。

测试结果如下所示:
生成token:


刷新token:


不带token访问接口:

带token但是权限不够:
带token而且权限也够:


到这里关于 SpringBoot+SpringSecurity+JWT就基本结束了,这篇博客我重点是将如何通过代码来实现这个功能,关于代码本身没有很细致的介绍。关于代码功能的介绍,例如怎么通过注解配置权限控制,如何通过配置来进行权限控制,token更新和验证等等的细节,我会在我这篇博客的教学视频中介绍,视频地址:点击打开链接

GitHub地址:点击打开链接

如果大家对文章有什么问题或者疑意之类的,可以加我订阅号在上面留言,订阅号上面我会定期更新最新博客。如果嫌麻烦可以直接加我wechat:lzqcode

 



 


 



 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/546367
推荐阅读
相关标签
  

闽ICP备14008679号