当前位置: 当前位置:首页 >数据库 >基于国密SM4实现用户认证&授权 正文

基于国密SM4实现用户认证&授权

2025-11-04 23:40:24 来源:多维IT资讯作者:IT科技 点击:865次

JWT生成和认证的基于基本流程

JWT(JSON Web Token),是国密目前比较流行的用户身份验证解决方案。 下面是实现授权一个简化的时序图,用于说明JWT生成和认证的用户基本流程。

图片

引入 Spring Security 依赖

Spring Security 是认证一个功能强大且高度可定制的安全框架。

复制<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>1.2.3.4.

配置说明

在 Spring Security 配置文件中,基于我们通常需要做如下配置:

AuthenticationProvider实现类:用于自定义身份验证逻辑;Filter:用于验证 token 有效性;AuthenticationManager:用于接收并处理身份验证请求;PasswordEncoder:用于密码加密和验证;SecurityFilterChain:过滤器链;

图片

自定义PasswordEncoder

Spring Security的国密PasswordEncoder是用于进行密码加密和验证的接口。它是实现授权一个密码编码器,用于将用户的用户原始密码转换为安全的加密字符串,并在验证过程中将加密后的认证密码与用户提供的密码进行比较。PasswordEncoder接口的基于主要用于提供安全的密码存储和验证机制,以防止用户密码泄露时被恶意使用。国密它是实现授权一种重要的安全性措施,用于保护用户密码的服务器托管用户安全性。

Spring Security 提供了多种PasswordEncoder接口的认证实现类,包括:

BCryptPasswordEncoder:使用BCrypt算法进行密码哈希和验证。它是目前广泛使用的密码哈希算法之一,具有较高的安全性。NoOpPasswordEncoder:不进行任何密码编码和哈希操作,即明文存储密码。不推荐在生产环境中使用,仅用于测试目的。Pbkdf2PasswordEncoder:使用PBKDF2算法进行密码哈希和验证。它通过应用哈希函数多次迭代和盐值,增加了密码破解的难度。MessageDigestPasswordEncoder:使用指定的消息摘要算法(如MD5、SHA-1、SHA-256等)进行密码哈希和验证。

使用国密(SM4)算法

复制<!-- SM4依赖 --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15to18</artifactId> <version>1.71</version> </dependency>1.2.3.4.5.6. 自定义的 PasswordEncoder 复制import cn.hutool.core.util.CharsetUtil; import cn.hutool.crypto.SmUtil; import org.springframework.security.crypto.password.PasswordEncoder; import java.nio.charset.StandardCharsets; import java.util.Objects; public class Sm4PasswordEncoder implements PasswordEncoder { // key长度必须为16 private static final String KEY = "KeyMustBe16Size."; @Override public String encode(CharSequence rawPassword) { return SmUtil.sm4(KEY.getBytes(StandardCharsets.UTF_8)).encryptHex(rawPassword.toString()); } @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { return Objects.equals(rawPassword.toString(), SmUtil.sm4(KEY.getBytes(StandardCharsets.UTF_8)).decryptStr(encodedPassword, StandardCharsets.UTF_8)); } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.

需要实现PasswordEncoder接口的encode()和matches()方法。encode()方法用于对明文密码进行加密处理,matches()方法用于比较明文密码与加密后的密码是否匹配。

配置自定义的 PasswordEncoder 复制@Configuration @EnableWebSecurity public class WebSecurityConfig { // 其它代码 /** * 密码加密方式配置 */ @Bean public PasswordEncoder passwordEncoder() { return new Sm4PasswordEncoder(); } }1.2.3.4.5.6.7.8.9.10.11.12.

自定义 Filter 验证 token 有效性

实现UserDetailsService接口,用于获取用户详细信息

复制import org.yian.springboot.demo.entity.User; import org.yian.springboot.demo.service.RoleService; import org.yian.springboot.demo.service.UserService; import org.yian.springboot.demo.security.model.AuthUser; import org.yian.springboot.demo.util.WebUtil; import lombok.AllArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @Service @AllArgsConstructor public class UserDetailsServiceImpl implements UserDetailsService { @Resource private UserService userService; @Resource private RoleService roleService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userService.findByUsername(username); if (Objects.isNull(user)) { throw new UsernameNotFoundException("用户名或密码错误!"); } List<String> roleCodeList = roleService.findRoleCodesByUsername(username); List<GrantedAuthority> authorities = roleCodeList.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()); return new AuthUser(user.getId(), user.getRealName(), user.getAvatar(), user.getPhone(), user.getUsername(), user.getPassword(), authorities); } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.

UserDetailsServiceImpl类实现了UserDetailsService接口,源码库重写了loadUserByUsername方法,用于获取用户的详细信息。

图片

其中AuthUser为自定义认证用户信息类,代码如下:

复制import lombok.Getter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import java.util.Collection; @Getter public class AuthUser extends User { /** * 用户ID */ private final String userId; /** * 真实姓名 */ private final String realName; /** * 电话 */ private final String phone; /** * 头像 */ private final String avatar; public AuthUser(String userId, String realName, String avatar, String phone, String username, String password, Collection<? extends GrantedAuthority> authorities) { super(username, password, true, true, true, true, authorities); this.userId = userId; this.realName = realName; this.avatar = avatar; this.phone = phone; } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.

AuthUser继承org.springframework.security.core.userdetails.User,添加了一些业务属性。

自定义 Filter 验证 token 有效性:

复制import org.yian.springboot.demo.constant.AuthConstant; import org.yian.springboot.demo.util.JwtUtil; import cn.hutool.core.util.StrUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.web.filter.OncePerRequestFilter; import javax.annotation.Resource; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 验证token有效性 */ @Slf4j public class TokenFilter extends OncePerRequestFilter { @Resource private UserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String token = getToken(request); if (StrUtil.isNotEmpty(token)) { // 从Token中获取username String username = JwtUtil.getUsernameFromToken(token); // 根据username获取用户信息 UserDetails userDetails = userDetailsService.loadUserByUsername(username); // 创建身份验证对象 Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); // 设置身份验证对象 SecurityContextHolder.getContext().setAuthentication(authentication); } // 过滤器链 filterChain.doFilter(request, response); } private String getToken(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (StrUtil.isNotEmpty(bearerToken) && bearerToken.startsWith(AuthConstant.AUTHORIZATION_BEARER)) { // 去掉令牌前缀 return bearerToken.replace(AuthConstant.AUTHORIZATION_BEARER, StrUtil.EMPTY); } return null; } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.

图片

配置自定义的自定义Filter:

复制@Configuration @EnableWebSecurity public class WebSecurityConfig { // 其它代码 @Bean public TokenFilter tokenFilter() { return new TokenFilter(); } }1.2.3.4.5.6.7.8.9.

配置 AuthenticationProvider

复制@Configuration @EnableWebSecurity public class WebSecurityConfig { // 其它代码 @Resource private UserDetailsServiceImpl userDetailsService; @Bean public DaoAuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(userDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.

DaoAuthenticationProvider是Spring Security提供的一个身份验证实现类,它使用数据库中的用户详细信息和密码加密器进行身份验证。

配置 AuthenticationManager

复制@Configuration @EnableWebSecurity public class WebSecurityConfig { // 其它代码 @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { return authConfig.getAuthenticationManager(); } }1.2.3.4.5.6.7.8.9.10.11.

配置过滤器链

自定义类,处理未经身份验证或者身份验证失败的用户访问受保护资源时的行为。

复制import org.yian.springboot.demo.api.Result; import org.yian.springboot.demo.api.ResultCode; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; 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; /** * 处理未经身份验证或者身份验证失败的用户访问受保护资源时的行为 */ @Component public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { String msg = StrUtil.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI()); response.setStatus(200); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.getWriter().print(JSONUtil.toJsonStr(Result.fail(ResultCode.UNAUTHORIZED, msg)));} }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.

配置AuthenticationManager:

复制import org.yian.springboot.demo.security.crypto.Sm4PasswordEncoder; import org.yian.springboot.demo.security.filter.TokenFilter; import org.yian.springboot.demo.service.impl.UserDetailsServiceImpl; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; 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.password.PasswordEncoder; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import javax.annotation.Resource; @Configuration @EnableWebSecurity public class WebSecurityConfig { @Resource private UserDetailsServiceImpl userDetailsService; @Resource private AuthenticationEntryPoint authenticationEntryPoint; @Bean public DaoAuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(userDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } @Bean public TokenFilter tokenFilter() { return new TokenFilter(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception { return authConfig.getAuthenticationManager(); } /** * 密码加密方式配置 */ @Bean public PasswordEncoder passwordEncoder() { return new Sm4PasswordEncoder(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // 启用跨域资源共享(CORS)支持 http.cors() .and() // 禁用跨站请求伪造(CSRF)保护 .csrf().disable() // 配置异常处理和身份验证入口点 .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint) .and() // 配置会话管理和会话创建策略:不使用会话 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() // 配置请求授权规则 .authorizeRequests().antMatchers("/api/test/**").permitAll() .antMatchers("/api/auth/**").permitAll() // 所有其他请求需要进行身份验证 .anyRequest().authenticated(); // 配置用户身份验证逻辑 http.authenticationProvider(authenticationProvider()); // 在UsernamePasswordAuthenticationFilter过滤器之前添加TokenFilter http.addFilterBefore(tokenFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.80.81.82.

登录接口

复制import org.yian.springboot.demo.api.Result; import org.yian.springboot.demo.security.model.AuthUser; import org.yian.springboot.demo.util.JwtUtil; import lombok.AllArgsConstructor; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; @RestController @AllArgsConstructor @RequestMapping("/api/auth") public class LoginController { private final AuthenticationManager authenticationManager; @PostMapping("/login") public Result<Map<String, Object>> login(String username, String password) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); Authentication authentication = authenticationManager.authenticate(authenticationToken); SecurityContextHolder.getContext().setAuthentication(authentication); String token = JwtUtil.createToken(username, new HashMap<>()); AuthUser authUser = (AuthUser) authentication.getPrincipal(); Map<String, Object> resultMap = new HashMap<>(16); resultMap.put("token", token); resultMap.put("user", authUser); return Result.success(resultMap); } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35. login()方法接收两个参数:username和password,表示用户输入的用户名和密码。根据用户名、密码创建一个UsernamePasswordAuthenticationToken对象;然后调用authenticationManager.authenticate(authenticationToken)方法,使用AuthenticationManager对身份验证令牌进行身份验证,得到一个已经通过身份验证的Authentication对象;然后调用SecurityContextHolder.getContext().setAuthentication(authentication)方法,将验证后的Authentication对象存储到SecurityContextHolder中,以便对用户进行身份认证;调用JWT工具类生成token 。调用authentication.getPrincipal()方法获取经过验证的用户信息,站群服务器强制类型转换为AuthUser类型。统一放在Map中返回。

测试

认证成功返回结果截图:

图片

认证失败返回结果截图:

图片

作者:数据库
------分隔线----------------------------
头条新闻
图片新闻
新闻排行榜