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
- Criar um projeto básico do SpringBoot
- Projeto SpringBoot integra mybatis
- O SpringBoot integra a fonte de dados Druid [SpringBoot series 3]
- SpringBoot MyBatis implementa dados de consulta de página [SpringBoot series 4]
- Integração SpringBoot MyBatis-Plus [SpringBoot Series 5]
- Gerador de código SpringBoot mybatis-plus-generator [SpringBoot series 6]
- Consulta de paginação SpringBoot MyBatis-Plus [SpringBoot Series 7]
- # O SpringBoot integra o cache Redis e implementa o cache de dados básico [SpringBoot series 8]
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.
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 interface
Em seguida, chame a interface de login para gerar o token
Em seguida, coloque as informações do cabeçalho da solicitação acessando outras interfaces
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"