Springboot+Spring Security实现前后端分离登录认证及权限控制的示例代码

下面是详细的讲解“Springboot+Spring Security实现前后端分离登录认证及权限控制的示例代码”的攻略。

1. Spring Security简介

Spring Security 是一个强大且高度可定制的身份验证和访问控制框架,与 Spring 应用程序无缝集成,具有广泛的可用插件和扩展点以满足几乎任何身份验证和授权要求。Spring Security 提供的安全功能包括认证、授权、防止 CSRF、会话管理等。

2. Springboot+Spring Security实现前后端分离登录认证及权限控制

下面是Springboot+Spring Security实现前后端分离登录认证及权限控制的步骤。

2.1 修改pom.xml文件

添加以下依赖:

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

<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-test</artifactId>
  <scope>test</scope>
</dependency>

2.2 编写Spring Security配置类

新建一个类SecurityConfig,使用@EnableWebSecurity注解开启Spring Security支持,并继承WebSecurityConfigurerAdapter类。重写configure方法,设置HttpSecurity配置。示例代码如下:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private AuthenticationEntryPoint unauthorizedHandler;

    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
                .userDetailsService(this.userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

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

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // 禁用 CSRF
                .csrf().disable()

                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()

                // 不创建会话
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

                .authorizeRequests()
                // 允许匿名访问的URL
                .antMatchers(
                        HttpMethod.GET,
                        "/",
                        "/v2/api-docs",           // swagger api json
                        "/swagger-resources/**",  // swagger 资源
                        "/swagger-ui.html",
                        "/webjars/**"
                ).permitAll()
                // 所有请求需要身份认证
                .anyRequest().authenticated();

        // 添加JWT过滤器
        JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter();
        jwtAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
        httpSecurity.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        //添加自定义的AccessDecisionManager
        httpSecurity.authorizeRequests().accessDecisionManager(updateAccessDecisionManager());
    }

    private AccessDecisionManager updateAccessDecisionManager() {
        List<AccessDecisionVoter<? extends Object>> decisionVoters
                = Arrays.asList(new RoleVoter(), new AuthenticatedVoter());

        return new AffirmativeBased(decisionVoters);
    }
}

2.3 编写Spring Security过滤器

新建一个类JwtAuthenticationFilter,继承OncePerRequestFilter类,实现JSON Web Token(JWT)的认证。具体实现步骤如下:

  1. 从请求头获取JWT,判断是否存在。
  2. 如果JWT存在,使用JWT解码出载荷中的用户名。
  3. 从数据库查询该用户名对应的用户对象。
  4. 构造一个Token对象(UsernamePasswordAuthenticationToken)。
  5. 将Token传入SecurityContextHolder中。

示例代码如下:

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtTokenProvider jwtTokenProvider;

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest,
                                    HttpServletResponse httpServletResponse,
                                    FilterChain filterChain) throws ServletException, IOException {
        try {
            String jwt = getJwtFromRequest(httpServletRequest);

            if (StringUtils.hasText(jwt) && jwtTokenProvider.validateToken(jwt)) {
                String username = jwtTokenProvider.getUsernameFromJWT(jwt);

                UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);
                UsernamePasswordAuthenticationToken authentication =
                        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));

                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception ex) {
            logger.error("Could not set user authentication in security context", ex);
        }

        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }

    private String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader(JwtTokenProvider.AUTHORIZATION_HEADER);

        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(JwtTokenProvider.TOKEN_PREFIX)) {
            return bearerToken.substring(JwtTokenProvider.TOKEN_PREFIX.length());
        }
        return null;
    }
}

2.4 编写JWT认证的Provider

新建一个类JwtTokenProvider,实现JWT的生成和解码。具体实现步骤如下:

  1. 使用JWT的默认算法HMAC256,使用自定义加密的KEY。
  2. 生成JWT时,使用当前的时间和有效期作为payload。
  3. 解码JWT时,使用JWT的包含的信息对JWT进行解码。

示例代码如下:

@Component
public class JwtTokenProvider {

    public static final String AUTHORIZATION_HEADER = "Authorization";
    public static final String TOKEN_PREFIX = "Bearer ";
    private final String secretKey = "mySecretKey";

    public String generateToken(Authentication authentication) {
        CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal();

        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + 3600 * 1000);

        return Jwts.builder()
                .setSubject(customUserDetails.getUsername())
                .setIssuedAt(new Date())
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, secretKey)
                .compact();
    }

    public String getUsernameFromJWT(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token)
                .getBody();

        return claims.getSubject();
    }

    public boolean validateToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(secretKey).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException ex) {
            logger.error("Invalid JWT signature");
        } catch (MalformedJwtException ex) {
            logger.error("Invalid JWT token");
        } catch (ExpiredJwtException ex) {
            logger.error("Expired JWT token");
        } catch (UnsupportedJwtException ex) {
            logger.error("Unsupported JWT token");
        } catch (IllegalArgumentException ex) {
            logger.error("JWT claims string is empty.");
        }
        return false;
    }
}

2.5 编写自定义UserDetailsService类

新建一个类CustomUserDetailsService,实现Spring Security的UserDetailsService接口。具体实现步骤如下:

  1. 从数据库获取用户信息(适配自己的数据库)。
  2. 如果查询到了用户信息,则构造一个Spring Security的UserDetails对象。

示例代码如下:

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    @Transactional
    public UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException {
        User user = userRepository.findByUsernameOrEmail(usernameOrEmail, usernameOrEmail)
                .orElseThrow(() ->
                        new UsernameNotFoundException("User not found with username or email : " + usernameOrEmail)
                );

        return CustomUserDetails.create(user);
    }
}

2.6 编写Controller

新建一个类UserController,实现Spring MVC的Controller。示例代码如下:

@RestController
@RequestMapping("/api/v1/user")
public class UserController {

    @PreAuthorize("hasAnyRole('ADMIN', 'USER')")
    @GetMapping("/info")
    public String getUserInfo(@AuthenticationPrincipal CustomUserDetails customUserDetails) {
        return "Hello, " + customUserDetails.getUsername();
    }
}

3. 示例

3.1 登录认证

前端使用POST请求 /login 接口,将用户名和密码发送到后端。后端使用Spring Security的实现类进行认证,认证成功后生成JWT,并将JWT发送到前端。

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

    @Autowired
    private JwtTokenProvider jwtTokenProvider;

    @Autowired
    private AuthenticationManager authenticationManager;

    @PostMapping("/login")
    public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        loginRequest.getUsernameOrEmail(),
                        loginRequest.getPassword()
                )
        );

        SecurityContextHolder.getContext().setAuthentication(authentication);

        String jwt = jwtTokenProvider.generateToken(authentication);
        return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
    }
}

3.2 访问控制

在Controller中使用@PreAuthorize注解控制方法的访问权限。示例代码如下:

@RestController
@RequestMapping("/api/v1/user")
public class UserController {

    @PreAuthorize("hasAnyRole('ADMIN', 'USER')")
    @GetMapping("/info")
    public String getUserInfo(@AuthenticationPrincipal CustomUserDetails customUserDetails) {
        return "Hello, " + customUserDetails.getUsername();
    }
}

在loadUserByUsername方法中将查询到的用户信息封装成Spring Security的UserDetails对象,并构造了一个CustomUserDetails对象。示例代码如下:

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    @Transactional
    public UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException {
        User user = userRepository.findByUsernameOrEmail(usernameOrEmail, usernameOrEmail)
                .orElseThrow(() ->
                        new UsernameNotFoundException("User not found with username or email : " + usernameOrEmail)
                );

        return CustomUserDetails.create(user);
    }
}

4. 总结

本文详细讲解了如何使用Springboot+Spring Security实现前后端分离登录认证及权限控制,并提供了示例代码。在实践中,需要根据自己的具体需求和业务逻辑进行调整和实现。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Springboot+Spring Security实现前后端分离登录认证及权限控制的示例代码 - Python技术站

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

相关文章

  • Java pom.xml parent引用报错问题解决方案

    针对Java pom.xml parent引用报错问题,下面是完整的解决方案攻略。 问题描述 在Maven项目中,我们经常会在子项目的pom.xml文件中引用父项目的依赖或配置信息。通常使用<parent>元素引用父pom.xml文件的配置。但是,在实际开发过程中,我们可能会遇到以下错误: Project build error: Non-res…

    Java 2023年5月19日
    00
  • Java多线程下的其他组件之CyclicBarrier、Callable、Future和FutureTask详解

    Java多线程下的其他组件之CyclicBarrier CyclicBarrier概述 CyclicBarrier是Java中一个同步工具类,用于在多线程中等待所有线程到达某个同步点,然后再一起执行后续操作,这个同步点就是所谓的屏障(barrier),它可重用,即当到达屏障的线程数量达到指定值时,所有线程都可以通过屏障,继续执行下一个操作。 CyclicBa…

    Java 2023年5月18日
    00
  • JAVA实现按时间段查询数据操作

    JAVA实现按时间段查询数据操作的完整攻略如下: 步骤一:连接数据库 首先,需要在Java代码中连接到数据库。一般使用JDBC驱动连接数据库。以下是连接MySQL数据库的示例代码: import java.sql.*; public class MySqlDatabase { private static final String DRIVER_NAME =…

    Java 2023年5月20日
    00
  • Java实现飞机大战-连接数据库并把得分写入数据库

    Java实现飞机大战-连接数据库并把得分写入数据库的攻略如下: 第一步:建立数据库 创建一个数据库,可使用MySQL或其他数据库软件,此处以MySQL为例。 在该数据库下创建一个用户,拥有读写权限。 创建一个存储分数的数据表,可命名为score,包含两个字段,一个为id,一个为score。 示例代码如下: CREATE DATABASE games; GRA…

    Java 2023年5月20日
    00
  • Java I/O流之打印流详细使用方法教程

    下面就为您详细讲解 Java I/O 流之打印流的详细使用方法教程。 简介 Java 提供了多种 I/O 流来处理输入输出操作,其中打印流(PrintStream 和 PrintWriter)可以方便地格式化输出文本。本文将着重介绍打印流的使用方法。 打印流的创建 创建打印流对象的方式与创建其他 I/O 流类似,通常需要指定输出目标。以下是创建打印流对象的两…

    Java 2023年5月26日
    00
  • Java的异常类型总结

    以下是Java的异常类型总结的完整攻略: Java的异常类型总结 在Java程序中,当运行时出现异常情况时会抛出异常,这时程序会中断并把错误信息输出到控制台。Java中异常分为两种类型:已检查异常和未检查异常。 已检查异常(Checked Exceptions) 已检查异常是指在编写Java程序时,编译器要求必须对可能出现该异常的代码进行处理或者声明抛出异常…

    Java 2023年5月27日
    00
  • BMIDE环境导入项目报编码错误解决方案

    下面是详细的BMIDE环境导入项目报编码错误解决方案攻略: 问题描述 当我们使用BMIDE环境导入项目时,可能会遇到编码错误的问题。具体表现为打开BMIDE后,选择需要导入的项目后点击“确定”按钮,但出现了以下错误提示信息: The project description ‘`’ should be a dirname representing a loca…

    Java 2023年5月20日
    00
  • java聊天室的实现代码

    下面我会为您详细讲解java聊天室的实现代码攻略。具体的实现过程分为以下几个步骤: 1. 创建服务器端 在服务器端,我们需要进行以下操作: 1.1 创建服务器套接字 服务器套接字是接受客户端连接的初始点。我们可以使用 ServerSocket 类来创建套接字,并指定服务器的监听端口号。 int portNumber = 1234; ServerSocket …

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