SpringCloud 대규모 과정 시리즈가 제작 중입니다. 여러분의 관심과 의견을 환영합니다.
프로그래머의 일일 CV 및 브릭뿐만 아니라 이유도 알고 있는 이 과정 시리즈는 초보자가 SpringBooot 프로젝트 개발 및 SpringCloud 마이크로서비스 시리즈 프로젝트 개발을 배우는 데 도움이 될 수 있습니다.
1 프로젝트 준비
- SpringBoot 기본 프로젝트 생성
- SpringBoot 프로젝트는 mybatis를 통합합니다.
- SpringBoot는 Druid 데이터 소스를 통합합니다 [SpringBoot 시리즈 3]
- SpringBoot MyBatis는 페이지 쿼리 데이터 구현 [SpringBoot 시리즈 4]
- SpringBoot MyBatis-Plus 통합 [SpringBoot 시리즈 5]
- SpringBoot mybatis-plus-generator 코드 생성기 [SpringBoot 시리즈 6]
- SpringBoot MyBatis-Plus 페이징 쿼리 [SpringBoot 시리즈 7]
- # SpringBoot는 Redis 캐시를 통합하고 기본 데이터 캐시를 구현합니다 [SpringBoot 시리즈 8]
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에 주의를 기울일 수 있습니다.
이 글은 "골든스톤 프로젝트" 에 참여하고 있습니다.