SpringBoot는 Spring Security를 통합하여 보안 인증을 달성합니다 [SpringBoot Series 9]

SpringCloud 대규모 과정 시리즈가 제작 중입니다. 여러분의 관심과 의견을 환영합니다.

프로그래머의 일일 CV 및 브릭뿐만 아니라 이유도 알고 있는 이 과정 시리즈는 초보자가 SpringBooot 프로젝트 개발 및 SpringCloud 마이크로서비스 시리즈 프로젝트 개발을 배우는 데 도움이 될 수 있습니다.

1 프로젝트 준비

Spring Security( 공식 웹 사이트는 여기 )는 Spring 커뮤니티의 최상위 프로젝트이며 Spring Boot에서 공식적으로 권장하는 보안 프레임워크입니다.여기에 이미지 설명 삽입

이 글은 인증 확인 기능을 구현하기 위해 SpringBoot의 Spring Security 통합을 구현한 것입니다.


먼저 다음과 같이 프로젝트의 pom.xml에 종속성을 추가합니다.

 <dependency>
       <groupId>com.alibaba</groupId>
       <artifactId>fastjson</artifactId>
       <version>2.0.25</version>
   </dependency>

   <dependency>
       <groupId>io.jsonwebtoken</groupId>
       <artifactId>jjwt-api</artifactId>
       <version>0.11.5</version>
   </dependency>

   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-security</artifactId>
       <version>3.0.4</version>
   </dependency>
复制代码

JWT(JSON 웹 토큰)는 토큰을 생성하는 데 사용되며 JWT는 실제로 헤더, 페이로드 및 서명의 세 부분으로 구성된 문자열입니다.

종속성을 추가한 후 프로젝트를 시작하고 브라우저의 모든 인터페이스에 액세스하면 로그인 인증이 나타납니다.

1 jwt 생성 토큰 도구

여기서는 사용자의 username + key에 따라 토큰을 생성한 후 Spring Security 인증 과정에서 사용되는 토큰 등을 복호화하는 것이다.

import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Date;

@Component
@Slf4j
public class JWTGenerator {
    //密钥
    private static String sign ="cuAihCz53DZRjZwbsGcZJ2Ai6At+T142uphtJMsk7iQ=";
    //生成token
    public String generateToken(Authentication authentication) {
        //用户的核心标识
        String username = authentication.getName();
        // 过期时间 - 30分钟
        Date expireDate = new Date(System.currentTimeMillis() + 30 * 60 * 1000);
        String token = Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(expireDate)
                .signWith(generalKeyByDecoders())  //设置token加密方式和密
                .compact();
        return token;
    }

    public static SecretKey generalKeyByDecoders() {
        return Keys.hmacShaKeyFor(Decoders.BASE64.decode(sign));
    }

    /**
     * 解密token
     * @param token
     * @return
     */
    public String getUsernameFromJWT(String token) {
        JwtParserBuilder builder = Jwts.parserBuilder();
        Jws<Claims> claimsJws = builder
                .setSigningKey(generalKeyByDecoders())
                .build()
                .parseClaimsJws(token);
        return claimsJws.getBody().getSubject();
    }

    /**
     * 校验token
     * @param token
     * @return
     */
    public boolean validateToken(String token) {
        log.error("验证 token  {}", token);
        try {
            JwtParserBuilder builder = Jwts.parserBuilder();

            Jws<Claims> claimsJws = builder
                    .setSigningKey(generalKeyByDecoders())
                    .build()
                    .parseClaimsJws(token);
            return true;
        } catch (ExpiredJwtException e) {
            Claims claims = e.getClaims();
            // 检查token
            throw new BadCredentialsException("TOKEN已过期,请重新登录!");
        } catch (AuthenticationException e) {
            throw new AuthenticationCredentialsNotFoundException("JWT was expired or incorrect");
        } catch (Exception ex) {
            log.error("token认证失败 {}", ex.getMessage());
            throw new AuthenticationCredentialsNotFoundException("JWT was expired or incorrect");
        }
    }
}
复制代码

2 로그인 인증 컨트롤러 정의

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

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private JWTGenerator jwtGenerator;

    @PostMapping("login")
    public R login(@RequestBody LoginRequest loginDto){
        log.info("登录认证开始 {}",loginDto.toString());
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        loginDto.getUserName(),
                        loginDto.getPassword()));
        // 认证成功存储认证信息到上下文
        SecurityContextHolder.getContext().setAuthentication(authentication);
        log.info("登录认证完成 {}",loginDto.toString());
        String token = jwtGenerator.generateToken(authentication);
        log.info("登录认证生成 token {}",token);
        return R.okData(token);
    }
 }
复制代码
  • JWTGenerator의 첫 번째 단계에서 정의한 토큰 생성 도구는 로그인 인증이 완료되면 토큰을 생성합니다.

  • AuthenticationManager는 인증 성공 여부에만 관심이 있고 특정 인증 방법에는 관심이 없으며 인증에 성공하면 완전히 채워진 인증 개체(허가된 권한 포함)를 반환합니다.

import lombok.Data;
import lombok.ToString;

import java.io.Serializable;
@Data
@ToString
public class LoginRequest implements Serializable {
    private String userName ;
    private String password;
}

复制代码

3코어 구성 SecurityConfig

SecurityConfig는 Spring Security의 차단 전략 및 인증 전략 등을 구성하는 데 사용됩니다.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    //自定义异常认证处理
    private JwtAuthEntryPoint authEntryPoint;
    //自定义授权异常处理
    private MyAccessDeniedHandler myAccessDeniedHandler;

    @Autowired
    public SecurityConfig(JwtAuthEntryPoint authEntryPoint, MyAccessDeniedHandler myAccessDeniedHandler) {
        this.authEntryPoint = authEntryPoint;
        this.myAccessDeniedHandler = myAccessDeniedHandler;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .exceptionHandling()
                .accessDeniedHandler(myAccessDeniedHandler)
                .authenticationEntryPoint(authEntryPoint)
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                .and()
                .authorizeRequests()
                //放行静态资源文件夹(路径要具体情况具体分析)
                .antMatchers(
                        "/api/auth/**",
                        "/css/**", "/js/**", "/image/**",
                        "/app/**",
                        "/swagger/**",
                        "/swagger-ui.html",
                        "/app/**",
                        "/swagger-resources/**",
                        "/v2/**",
                        "/webjars/**").permitAll()

                .anyRequest().authenticated()
                .and()
                .httpBasic();
        http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    @Bean
    public AuthenticationManager authenticationManager(
            AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

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

 // 自定义 认证过滤器
    @Bean
    public JWTAuthenticationFilter jwtAuthenticationFilter() {
        return new JWTAuthenticationFilter();
    }
}
复制代码
3.1 JwtAuthEntryPoint 커스텀 인증 실패 콜백 처리
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Component
@Slf4j
public class JwtAuthEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {

        String message = authException.getMessage();
        log.error("token 拦截 {}",message);
        response.setCharacterEncoding("utf-8");
        response.setContentType("text/javascript;charset=utf-8");
        Map<String,Object> map = new HashMap<>();
        map.put("code",403);
        map.put("message","您未登录,没有访问权限");
        response.getWriter().print(JSONObject.toJSONString(map));
    }
}
复制代码
3.2 MyAccessDeniedHandler 사용자 정의 인증 실패 콜백 처리
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 授权异常
 */
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException, IOException {
        response.setStatus(403);
        response.getWriter().write("Forbidden:" + accessDeniedException.getMessage());
    }
}
复制代码

4코어 필터 JWTAuthenticationFilter

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class JWTAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JWTGenerator tokenGenerator;
    @Autowired
    private CustomUserDetailsService customUserDetailsService;


    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        //获取请求头中的 token 信息
        String token = getJWTFromRequest(request);
        //校验token
        if(StringUtils.hasText(token) && tokenGenerator.validateToken(token)) {
            //解析 token 中的用户信息 (用户的唯一标识 )
            String username = tokenGenerator.getUsernameFromJWT(token);

            UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null,
                    userDetails.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        filterChain.doFilter(request, response);
    }

    /**
     * 就是校验请求头的一种格式 可以随便定义
     * 只要可以解析 就可以
     * @param request
     * @return
     */
    private String getJWTFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7, bearerToken.length());
        }
        return null;
    }
复制代码

5 CustomUserDetailsService 사용자 확인 구현

@Service
public class CustomUserDetailsService  implements UserDetailsService {

    private UserService userService;

    @Autowired
    public CustomUserDetailsService(UserService userService) {
        this.userService = userService;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserInfo user = userService.getByUsername(username);
        if(user==null){
            throw new  UsernameNotFoundException("Username not found");
        }
        User user1 = new User(user.getUserName(), user.getPassword(), mapRolesToAuthorities(user.getRoles()));
        return user1;
    }

    private Collection<GrantedAuthority> mapRolesToAuthorities(List<Role> roles) {
        return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList());
    }
}
复制代码

여기서 사용되는 UserService는 프로젝트에서 사용자 정보를 조회하기 위한 서비스입니다. 그런 다음 Postman을 사용하여 인터페이스에 액세스하십시오.여기에 이미지 설명 삽입

그런 다음 로그인 인터페이스를 호출하여 토큰을 생성합니다.

여기에 이미지 설명 삽입그런 다음 다른 인터페이스에 액세스할 때 요청 헤더 정보를 넣습니다.여기에 이미지 설명 삽입

프로젝트의 소스 코드는 다음과 같습니다: gitee.com/android.lon ... 관심이 있는 경우 공식 계정인 biglead에 주의를 기울일 수 있습니다.

이 글은 "골든스톤 프로젝트" 에 참여하고 있습니다.

Supongo que te gusta

Origin juejin.im/post/7215478156949422136
Recomendado
Clasificación