SpringBoot 整合 Spring Security 实现安全认证【SpringBoot系列9】

SpringBoot 整合 Spring Security 实现安全认证【SpringBoot系列9】,第1张

1 项目准备

SpringBoot 搭建项目 【SpringBoot系列1】

SpringBoot 集成Redis缓存 以及实现基本的数据缓存【SpringBoot系列8】本文章在此基础上进行搭建。

Spring Security 是 Spring 社区的一个顶级项目,也是 Spring Boot 官方推荐使用的安全框架。官网在这里:

https://spring.io/projects/spring-security

SpringBoot 整合 Spring Security 实现安全认证【SpringBoot系列9】,第2张

本文章实现的是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 Web Token) 用来生成 Token ,JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。

添加依赖后,启动项目,在浏览器中访问任何一个接口都会出现 登录认证

1 jjwt 生成 token 工具

这里就是根据 用户的 username + 密钥来生成token ,然后解密 token 等等,在 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@Slf4jpublic 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 登录认证 Controller 定义
@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 第一步中定义的 token 生成工具,在登录校验完成时,生成token

AuthenticationManager 只关注认证成功与否而并不关心具体的认证方式,如果验证成功,则返回完全填充的Authentication对象(包括授予的权限)。

import lombok.Data;import lombok.ToString;
import java.io.Serializable;@Data@ToStringpublic 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@EnableWebSecuritypublic 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@Slf4jpublic 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;
/** * 授权异常 */@Componentpublic 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 用户校验实现
@Servicepublic 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 来访问接口

SpringBoot 整合 Spring Security 实现安全认证【SpringBoot系列9】,第3张

然后调用 登录接口生成 token

SpringBoot 整合 Spring Security 实现安全认证【SpringBoot系列9】,第4张

然后在访问其他接口里放入请求头信息

SpringBoot 整合 Spring Security 实现安全认证【SpringBoot系列9】,第5张

项目源码在这里 

https://gitee.com/android.long/spring-boot-study/tree/master/biglead-api-07-security
DABAN RP主题是一个优秀的主题,极致后台体验,无插件,集成会员系统
白度搜_经验知识百科全书 » SpringBoot 整合 Spring Security 实现安全认证【SpringBoot系列9】

0条评论

发表评论

提供最优质的资源集合

立即查看 了解详情