O SpringBoot integra o Spring Security para obter autenticação de segurança [SpringBoot Series 9]

A série de cursos em larga escala do SpringCloud está em produção, agradecemos sua atenção e comentários.

Currículo diário e tijolos dos programadores, mas também saber o porquê, esta série de cursos pode ajudar os iniciantes a aprender o desenvolvimento de projetos SpringBooot e o desenvolvimento de projetos da série de microsserviços SpringCloud

1 preparação do projeto

Spring Security ( site oficial aqui ) é um projeto de alto nível na comunidade Spring e a estrutura de segurança oficialmente recomendada pelo Spring Boot.insira a descrição da imagem aqui

Este artigo implementa a integração SpringBoot do Spring Security para implementar a função de verificação de autenticação. Existem muitas maneiras de implementá-lo. Este artigo é apenas um deles. Se houver alguma deficiência, deixe uma mensagem.


Primeiro adicione as dependências no pom.xml do projeto da seguinte forma:

 <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 Web Token) é usado para gerar o Token. JWT é na verdade uma string, que consiste em três partes, cabeçalho, carga útil e assinatura.

Após adicionar as dependências, inicie o projeto, e ao acessar qualquer interface no navegador aparecerá o login de autenticação

1 ferramenta de geração de token jwt

Aqui é para gerar um token de acordo com o nome de usuário + chave do usuário e, em seguida, descriptografar o token, etc., que são usados ​​no processo de autenticação do 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 Definição do Controlador de Autenticação de Login

@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);
    }
 }
复制代码
  • A ferramenta de geração de token definida na primeira etapa do JWTGenerator gera um token quando a verificação de login é concluída.

  • AuthenticationManager só se preocupa se a autenticação foi bem-sucedida ou não e não se importa com o método de autenticação específico. Se a autenticação for bem-sucedida, ele retornará um objeto Authentication totalmente preenchido (incluindo as permissões concedidas).

import lombok.Data;
import lombok.ToString;

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

复制代码

Configuração de 3 núcleos SecurityConfig

SecurityConfig é usado para configurar a estratégia de interceptação e estratégia de autenticação do Spring Security, etc.

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 Processamento de retorno de chamada de falha de autenticação personalizada 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 Processamento de retorno de chamada de falha de autorização personalizada 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());
    }
}
复制代码

Filtro de 4 núcleos 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 Implementação de verificação do usuário 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());
    }
}
复制代码

O UserService utilizado aqui é o serviço de consulta de informações do usuário no projeto. Em seguida, use o carteiro para acessar a interfaceinsira a descrição da imagem aqui

Em seguida, chame a interface de login para gerar o token

insira a descrição da imagem aquiEm seguida, coloque as informações do cabeçalho da solicitação acessando outras interfacesinsira a descrição da imagem aqui

O código-fonte do projeto está aqui: gitee.com/android.lon ... Se você estiver interessado, pode prestar atenção à conta oficial: biglead

Este artigo está participando do "Projeto Pedra Dourada"

Guess you like

Origin juejin.im/post/7215478156949422136