SpringSecurity+JWT实现前后端分离的使用详解

实现前后端分离的一个重要问题是如何进行身份验证和授权。Spring Security提供了一个非常方便的方法来处理这个问题,即使用JSON Web Token(JWT)。

JWT是一种用于身份验证和授权的开放标准,它定义了一种紧凑的、自包含的、可自校验的JSON格式来传递信息,通常用于在安全领域的传输而被广泛使用。

下面是SpringSecurity+JWT实现前后端分离的详细攻略:

1. 添加依赖

首先需要添加以下依赖到项目中,可以使用Maven或者Gradle,这里以Maven为例:

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

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

2. 配置Spring Security

配置Spring Security的基本步骤如下:

  1. 创建一个实现了UserDetailsService接口的类,该类用于从数据库中获取用户信息;

  2. 创建一个继承了WebSecurityConfigurerAdapter类的security配置类,并覆盖一些默认配置;

  3. 配置PasswordEncoder用于对用户密码进行加密。

以下是一个简单的security配置类:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers(HttpMethod.POST, "/api/authenticate").permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                .addFilter(new JWTAuthorizationFilter(authenticationManager()));
    }
}

在上面的配置中,我们禁用了CSRF防护,对于POST请求中的/authenticate接口我们允许所有人访问,其他所有请求都需要验证用户身份。

3. 实现JWT

在Spring Security的配置类中,我们添加了JWTAuthenticationFilter和JWTAuthorizationFilter。JWTAuthenticationFilter用于在验证用户名和密码之后,生成一个JWT并将其添加到Http的Header中返回给客户端;JWTAuthorizationFilter用于在客户端发送请求时,对JWT进行验证并允许或拒绝访问。

这里我们需要实现两个过滤器:

JWTAuthenticationFilter

JWTAuthenticationFilter中的实现如下:

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req,
                                                HttpServletResponse res) throws AuthenticationException {
        try {
            User creds = new ObjectMapper()
                    .readValue(req.getInputStream(), User.class);

            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            creds.getUsername(),
                            creds.getPassword(),
                            new ArrayList<>())
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest req,
                                            HttpServletResponse res,
                                            FilterChain chain,
                                            Authentication auth) throws IOException, ServletException {

        String token = JWT.create()
                .withSubject(((User) auth.getPrincipal()).getUsername())
                .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .sign(HMAC512(SECRET.getBytes()));

        res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
    }
}

在JWTAuthenticationFilter中,我们覆盖了attemptAuthentication方法,当用户输入用户名和密码时,这个方法会被调用并使用authenticationManager对用户名和密码进行验证。

在验证成功时,我们使用JWT库生成一个JWT token并将其添加到Http的Header中返回给客户端。

JWTAuthorizationFilter

JWTAuthorizationFilter中的实现如下:

public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

    public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest req,
                                    HttpServletResponse res,
                                    FilterChain chain) throws IOException, ServletException {

        String header = req.getHeader(HEADER_STRING);

        if (header == null || !header.startsWith(TOKEN_PREFIX)) {
            chain.doFilter(req, res);
            return;
        }

        UsernamePasswordAuthenticationToken authentication = getAuthentication(req);

        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(req, res);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        String token = request.getHeader(HEADER_STRING);
        if (token != null) {
            // parse the token.
            String user = JWT.require(HMAC512(SECRET.getBytes()))
                    .build()
                    .verify(token.replace(TOKEN_PREFIX, ""))
                    .getSubject();

            if (user != null) {
                return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
            }
            return null;
        }
        return null;
    }
}

在JWTAuthorizationFilter中,我们覆盖了doFilterInternal方法,当客户端在Http的Header中发送了一个名为Authorization的JWT token时,这个方法会被调用。

在该方法中,我们获取JWT token并对其进行验证,如果验证成功,将该用户的信息添加到SecurityContextHolder中,允许用户访问所请求的资源。

4. 控制器实现

在控制器中,需要提供一个/authenticate接口,用于验证用户身份并生成JWT token并返回给客户端。

以下是一个简单的控制器示例:

@RestController
@RequestMapping("/api")
public class AuthenticationController {

    private AuthenticationManager authenticationManager;

    @Autowired
    public AuthenticationController(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @PostMapping("/authenticate")
    public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {

        try {
            authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword())
            );
        } catch (BadCredentialsException e) {
            throw new Exception("Incorrect username or password", e);
        }

        final UserDetails userDetails = userDetailsService
                .loadUserByUsername(authenticationRequest.getUsername());

        final String jwt = jwtTokenUtil.generateToken(userDetails);

        return ResponseEntity.ok(new AuthenticationResponse(jwt));
    }
}

在上面的控制器中,我们提供了一个/api/authenticate接口,用于验证用户身份。

接口接收一个AuthenticationRequest对象,包含用户名和密码。

如果验证成功,则使用jwtTokenUtil生成JWT token并将其添加到AuthenticationResponse对象中返回给客户端。

5. 示例

以下是一个示例代码:

AuthenticationRequest

public class AuthenticationRequest {

    private String username;
    private String password;

    public AuthenticationRequest() {
    }

    public AuthenticationRequest(String username, String password) {
        this.username = username;
        this.password = password;
    }

    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;
    }
}

AuthenticationResponse

public class AuthenticationResponse {

    private final String jwt;

    public AuthenticationResponse(String jwt) {
        this.jwt = jwt;
    }

    public String getToken() {
        return jwt;
    }
}

User

public class User {

    private String username;
    private String password;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    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;
    }
}

测试用例

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class AuthenticationControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private UserRepository userRepository;

    @Before
    public void setUp() throws Exception {
        userRepository.deleteAll();
        userRepository.save(new User("admin", "password"));
    }

    @Test
    public void should_return_jwt_token_when_authenticate_given_valid_username_and_password() throws Exception {
        AuthenticationRequest authenticationRequest = new AuthenticationRequest("admin", "password");

        mockMvc.perform(post("/api/authenticate")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(authenticationRequest)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.token").isString());
    }

    @Test
    public void should_return_error_when_authenticate_given_invalid_username_and_password() throws Exception {
        AuthenticationRequest authenticationRequest = new AuthenticationRequest("invalid_username", "invalid_password");

        mockMvc.perform(post("/api/authenticate")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(authenticationRequest)))
                .andExpect(status().isUnauthorized());
    }

}

在测试用例中,我们使用MockMvc模拟一个HttpServletRequest并发送给/authenticate接口,使用ObjectMapper将AuthenticationRequest对象转换为JSON格式并将其传送给/authenticate接口。

如果验证成功,我们期望返回200 OK,并且返回的JSON对象应该包含一个名为token的字符串。如果验证失败,我们期望返回401 Unauthorized。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:SpringSecurity+JWT实现前后端分离的使用详解 - Python技术站

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

相关文章

  • Springmvc 4.x利用@ResponseBody返回Json数据的方法

    以下是关于“SpringMVC 4.x利用@ResponseBody返回JSON数据的方法”的完整攻略,其中包含两个示例。 SpringMVC 4.x利用@ResponseBody返回JSON数据的方法 在SpringMVC 4.x中,我们可以使用@ResponseBody注解将Java对象转换为JSON格式的数据,并将其返回给客户端。本文将介绍两个示例,包…

    Java 2023年5月16日
    00
  • logback自定义json日志输出示例详解

    对于这个话题,我会用标准的 Markdown 格式来回答,并且提供两个具体的示例说明。以下是完整攻略: logback自定义json日志输出示例详解 什么是logback? logback 是一个 Java 日志框架,与 log4j 相比具有更高的性能和更丰富的功能。 为什么需要自定义json日志输出? 在日志分析和处理上,json 格式的日志更加方便和易于…

    Java 2023年5月26日
    00
  • STRUTS+AJAX+JSP 请求到后台乱码问题解决方法

    针对 “STRUTS+AJAX+JSP 请求到后台乱码问题解决方法” 这个问题,我们需要分几个步骤来进行讲解。 步骤一:字符集设置 在 web.xml 文件中配置字符集编码为 UTF-8,以支持中文等特殊字符的传输。 <web-app> <filter> <filter-name>encodingFilter</fi…

    Java 2023年6月15日
    00
  • tomcat以及tomcat环境变量安装配置方法教程

    下面是详细的“Tomcat以及Tomcat环境变量安装配置方法教程”。 安装Tomcat 安装Tomcat可以按如下步骤进行: 前往Apache Tomcat官网下载Tomcat安装包。 解压安装包到任意目录,例如/usr/local/tomcat。 修改Tomcat配置文件conf/server.xml,根据需要修改端口和其他配置,例如: <Conn…

    Java 2023年5月19日
    00
  • 使用JPA进行CriteriaQuery进行查询的注意事项

    使用JPA进行CriteriaQuery进行查询时,需要注意以下几个方面: 1. 配置persistence.xml 首先,需要在persistence.xml文件中配置JPA的provider和数据库连接信息。在provider中需要指定使用Hibernate等JPA实现,以及指定JPA的版本。例如: <persistence-unit name=&…

    Java 2023年5月20日
    00
  • Java Spring 循环依赖解析

    下面是“Java Spring 循环依赖解析”的完整攻略。 什么是循环依赖? 在 Spring 容器中,如果两个或多个 Bean 相互依赖,且这种互相依赖形成了环路,就会出现循环依赖。 例如,BeanA依赖BeanB,而BeanB又依赖BeanA,则会形成一个循环依赖。 如何解决循环依赖? Spring 解决循环依赖的方式称为循环依赖解析。当 Spring …

    Java 2023年5月20日
    00
  • Java面向对象三大特性及多态解析

    Java面向对象编程是Java语言的基础,它支持三大特性:继承、封装和多态。其中,多态是Java最核心的特性,常常用于实现高效的代码重用和面向接口的编程。本篇攻略将详细讲解Java面向对象三大特性及多态解析。 一、继承 1.继承的定义 继承(Inheritance)是指一个类(称为子类、派生类)继承另外一个类(称为父类、基类)所有非私有的方法和属性。通过继承…

    Java 2023年5月26日
    00
  • Java中浮点数精度问题的解决方法

    下面是针对Java中浮点数精度问题的解决方法的完整攻略: 问题描述 Java中浮点数精度问题的主要表现是由于浮点数使用二进制进行存储和计算,而二进制表示法无法准确地表示所有的十进制小数。这种问题经常会导致在浮点数计算中出现较小的误差。下面是一个简要的示例: double a = 0.1; double b = 0.2; double c = a + b; S…

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