详解SpringBoot+SpringSecurity+jwt整合及初体验

详解SpringBoot+SpringSecurity+jwt整合及初体验

本文将详细讲解如何将SpringBoot、SpringSecurity和jwt整合起来实现用户认证与授权功能,包含完整的代码和详细的步骤,最终实现一个简单的用户登录验证功能。

环境准备

  • JDK 1.8
  • Maven 3.x
  • IDE: 推荐使用IntelliJ IDEA
  • Postman:用于测试接口

创建SpringBoot项目

在IDE中创建一个SpringBoot项目,选择Maven作为构建工具,在pom.xml文件中添加如下依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId> // web组件,提供web服务
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId> // security组件,提供安全控制
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId> // jwt组件,提供json web token相关功能
        <version>0.9.1</version>
    </dependency>
</dependencies>

配置SpringSecurity

在SpringBoot项目的application.properties文件中添加如下配置:

spring.security.user.name=admin
spring.security.user.password=admin
spring.security.user.roles=ADMIN

该配置表示创建一个用户名为admin,密码为admin,角色为ADMIN的用户。在实际应用中应该使用加密后的密码。

配置JWT

application.properties文件中添加如下配置:

jwt.secret=secret-key
jwt.expiration-in-seconds=604800

该配置表示使用secret-key作为jwt的密钥,jwt的过期时间为一周。

创建一个JwtUtils类用于生成和解析jwt:

@Component
public class JwtUtils {

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration-in-seconds}")
    private int expirationInSeconds;

    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(userDetails.getUsername())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expirationInSeconds * 1000))
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    public String getUsernameFromToken(String token) {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }

    public boolean validateToken(String token, UserDetails userDetails) {
        String username = getUsernameFromToken(token);
        return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
    }

    private boolean isTokenExpired(String token) {
        Date expirationDate = Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody()
                .getExpiration();
        return expirationDate.before(new Date());
    }
}

编写登录接口

创建一个AuthController类,实现用户登录接口:

@RestController
@RequestMapping("/api/auth")
public class AuthController {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtUtils jwtUtils;

    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody AuthenticationRequest request) {
        try {
            Authentication authentication = authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));
            SecurityContextHolder.getContext().setAuthentication(authentication);
            String token = jwtUtils.generateToken((UserDetails) authentication.getPrincipal());
            return ResponseEntity.ok(new AuthenticationResponse(token));
        } catch (AuthenticationException e) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
    }
}

其中AuthenticationRequest表示前端传递的登录请求:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AuthenticationRequest implements Serializable {
    private String username;
    private String password;
}

AuthenticationResponse表示登录成功后返回的jwt:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AuthenticationResponse implements Serializable {
    private String jwtToken;
}

配置Spring Security

创建一个JwtAuthenticationFilter类用于从Authorization头部解析jwt,并将用户信息存入Spring Security上下文:

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        String authorizationHeader = request.getHeader("Authorization");

        if (StringUtils.hasText(authorizationHeader) && authorizationHeader.startsWith("Bearer ")) {
            String jwt = authorizationHeader.substring(7);
            String username = jwtUtils.getUsernameFromToken(jwt);

            if (StringUtils.hasText(username) && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);

                if (jwtUtils.validateToken(jwt, userDetails)) {
                    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());
                    authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                }
            }
        }

        filterChain.doFilter(request, response);
    }
}

WebSecurityConfigurerAdapter中配置JwtAuthenticationFilter,并启用csrf保护:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/api/auth/**")
                .permitAll()
                .anyRequest()
                .authenticated();
        http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

测试登录接口

在Postman中发送POST请求:http://localhost:8080/api/auth/login,Headers中加入Content-Type: application/json,请求体中加入:

{
    "username": "admin",
    "password": "admin"
}

运行后会得到如下结果:

{
    "jwtToken": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImlhdCI6MTYzMzUzMjExOSwiZXhwIjoxNjMzNTU4MTE5fQ.2HVXYQNB5zWgR83SjlYvKvAD-VqyZTbEmyIy_t-sZJ9OdMCuLq0nLWOD4cT8QjL9wUyS9dUly4v6dFYJ7cmRXw"
}

将得到的jwt token放入请求头中继续发送请求,后端会自动将用户信息存入Spring Security上下文中,实现了用户认证与授权功能。

示例1:自定义UserDetailsService

如果需要使用自己实现的UserDetailsService来加载用户信息,可以按照下面的步骤来实现:

  1. 创建一个User类,表示应用中的用户:

```java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements UserDetails {

   private String username;
   private String password;
   private List<GrantedAuthority> authorities;

   @Override
   public Collection<? extends GrantedAuthority> getAuthorities() {
       return authorities;
   }

   @Override
   public String getPassword() {
       return password;
   }

   @Override
   public String getUsername() {
       return username;
   }

   @Override
   public boolean isAccountNonExpired() {
       return true;
   }

   @Override
   public boolean isAccountNonLocked() {
       return true;
   }

   @Override
   public boolean isCredentialsNonExpired() {
       return true;
   }

   @Override
   public boolean isEnabled() {
       return true;
   }

}
```

  1. 创建一个实现了UserDetailsService接口的类,用于从数据源中加载用户信息:

```java
@Service
public class MyUserDetailsService implements UserDetailsService {

   @Override
   public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
       // 查询数据库并返回UserDetails
       return new User(username, "password", List.of(new SimpleGrantedAuthority("ROLE_USER")));
   }

}
```

  1. WebSecurityConfigurerAdapter中注入UserDetailsService实现类,进行配置:

```java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   @Autowired
   private MyUserDetailsService userDetailsService;

   // 省略其他配置

}
```

示例2:自定义加密

默认情况下,Spring Security使用BCrypt进行加密,如果需要使用自己的加密方式,可以按照下面的步骤来实现:

  1. 创建一个实现了PasswordEncoder接口的类,用于加密和校验密码:

```java
@Component
public class MyPasswordEncoder implements PasswordEncoder {

   @Override
   public String encode(CharSequence rawPassword) {
       // 自定义加密方法
       return rawPassword.toString() + "hashed";
   }

   @Override
   public boolean matches(CharSequence rawPassword, String encodedPassword) {
       // 自定义校验方法
       return encodedPassword.equals(rawPassword.toString() + "hashed");
   }

}
```

  1. MyPasswordEncoder类注入到Spring容器中:

java
@Bean
public MyPasswordEncoder myPasswordEncoder() {
return new MyPasswordEncoder();
}

  1. WebSecurityConfigurerAdapter中配置使用自定义的PasswordEncoder

```java
@Autowired
private MyPasswordEncoder passwordEncoder;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
```

这样就可以使用自己的加密方式来保护密码了。

总结

本文介绍了如何将SpringBoot、SpringSecurity和jwt整合起来实现用户验证与授权功能,包含完整的代码和详细的步骤,最终实现了一个简单的用户登录验证功能,同时提供了两个实际应用的示例。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解SpringBoot+SpringSecurity+jwt整合及初体验 - Python技术站

(0)
上一篇 2023年5月20日
下一篇 2023年5月20日

相关文章

  • Springboot多数据源配置之整合dynamic-datasource方式

    Springboot多数据源配置之整合dynamic-datasource方式 在实际的应用开发中,我们往往需要连接多个数据库来存储不同的数据,而Springboot提供了多种方式来实现多数据源配置,其中一种方便易用的方式就是使用dynamic-datasource这个开源的库。 本文将介绍如何使用dynamic-datasource来配置Springboo…

    Java 2023年5月20日
    00
  • 编码为GB2312网站让AJAX接收的数据显示支持中文

    为了让 AJAX 接收的数据支持中文,我们需要考虑两个方面:编码和显示。 编码 首先,我们需要将网站的编码设置为 GB2312。这可以通过在 HTML head 标签中添加以下代码实现: <meta http-equiv="Content-Type" content="text/html; charset=gb2312&q…

    Java 2023年6月15日
    00
  • JavaSpringBoot报错“HttpMessageNotWritableException”的原因和处理方法

    原因 “HttpMessageNotWritableException” 错误通常是以下原因引起的: 响应体不正确:如果响应体不正确,则可能会出现此错误。在这种情况下,您需要检查响应体并确保它们正确。 响应体格式不正确:如果响应体格式不正确,则可能会出现此错误。在这种情况下,您需要检查响应体格式并确保它们正确。 解决办法 以下是解决 “HttpMessage…

    Java 2023年5月4日
    00
  • js实现翻页后保持checkbox选中状态的实现方法

    实现翻页后保持checkbox选中状态,需要将选中状态保存在本地存储中。在页面重新加载时,可以通过读取本地存储的值来恢复checkbox的选中状态。 以下是实现步骤: 1. 给checkbox设置一个唯一的标识符 在checkbox的HTML标签中加入一个唯一的标识符,以便在JavaScript中进行操作。 <input type="chec…

    Java 2023年6月16日
    00
  • Spring Security如何在Servlet中执行

    Spring Security 是 Spring 框架中的一个安全框架,可以用于保护 Web 应用程序的安全,包括身份验证、授权、防止攻击等功能。在 Servlet 中使用 Spring Security 可以有效地保护应用程序的安全,下面是详细的使用攻略。 1. 添加 Spring Security 依赖 首先,需要在项目中添加 Spring Securi…

    Java 2023年5月20日
    00
  • SpringBoot静态资源css,js,img配置方案

    下面我将为你详细讲解如何在Spring Boot中配置静态资源,即css、js和img文件。 一、默认静态资源位置 Spring Boot中默认情况下会自动加载如下两个目录下的静态资源: /static /public /resources /META-INF/resources 其中,/static和/public目录下的静态资源会直接映射到根路径下。例如…

    Java 2023年5月19日
    00
  • 把WebLogic EJB程序迁移到JBoss上

    把WebLogic EJB程序迁移到JBoss上的完整攻略包含以下步骤: 1. 准备工作 首先需要确认WebLogic EJB程序的版本,以及目标平台的JBoss版本,确保两者兼容。同时需要安装配置JBoss服务器,并确保数据库驱动在JBoss中可用。 2. 将EJB程序导出 在WebLogic控制台中找到需要迁移的EJB应用程序,对其进行导出并打包。这里以…

    Java 2023年6月15日
    00
  • SpringMVC接收页面表单参数

    SpringMVC是一个非常流行的Java Web框架,它提供了很多方便的功能。其中之一就是接收页面表单参数。本文将详细讲解如何使用SpringMVC接收页面表单参数,并提供两个示例来说明如何实现这一过程。 示例一:接收单个参数 以下是一个示例演示如何使用SpringMVC接收单个参数: 创建一个名为myproject的Maven项目。 添加SpringMV…

    Java 2023年5月17日
    00
合作推广
合作推广
分享本页
返回顶部