赞
踩
SpringBoot最新版本Security配置(2023年),亲测成功
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.6</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.yw</groupId> <artifactId>reviewTest</artifactId> <version>0.0.1-SNAPSHOT</version> <name>reviewTest</name> <description>reviewTest</description> <properties> <java.version>17</java.version> </properties> <dependencies> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jdbc --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> <version>3.0.2</version> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.26</version> <scope>provided</scope> </dependency> <!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.32</version> </dependency> <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3</version> </dependency> <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.3.1</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>3.0.2</version> </dependency> <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.jetbrains/annotations --> <dependency> <groupId>org.jetbrains</groupId> <artifactId>annotations</artifactId> <version>23.0.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> <version>3.0.6</version> </dependency> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.29</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
server.port=3000
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3307/kob?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
package com.yw.reviewtest.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; 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.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.config.ldap.LdapBindAuthenticationManagerFactory; 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; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; /** * @author yw~ * @version 1.0 */ @Configuration @EnableWebSecurity public class SecurityConfig { @Autowired private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // jwt 校验过滤器,从 http 头部 Authorization 字段读取 token 并校验 @Bean public JwtAuthenticationTokenFilter authFilter() throws Exception { return new JwtAuthenticationTokenFilter(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * 获取AuthenticationManager(认证管理器),登录时认证使用 * * @param authenticationConfiguration * @return * @throws Exception */ @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http // 基于 token,不需要 csrf .csrf().disable() // 基于 token,不需要 session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() // 前后端分离架构不需要csrf保护 .authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests // 允许所有OPTIONS请求 .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 允许直接访问授权登录接口 //一般浏览器访问url为get请求,而前端请求设置中为post,增加安全 .requestMatchers(HttpMethod.POST, "/user/account/token/").permitAll() // 允许 SpringMVC 的默认错误地址匿名访问 .requestMatchers("/error").permitAll() // 其他所有接口必须有Authority信息,Authority在登录成功后的UserDetailsImpl对象中默认设置“ROLE_USER” //.requestMatchers("/**").hasAnyAuthority("ROLE_USER") // 所有请求将会拦截,除了已经登录 .anyRequest().authenticated() ); //这里默认为security登录页面,在后续需要关闭时进行 // 添加 JWT 过滤器,JWT 过滤器在用户名密码认证过滤器之前 http.addFilterBefore(authFilter(), UsernamePasswordAuthenticationFilter.class); //使配置生效 return http.build(); } @Bean public WebSecurityCustomizer webSecurityCustomizer() { // 将前端请求全双工放行 //使用WebSecurity.ignoring()忽略某些URL请求,这些请求将被Spring Security忽略,这意味着这些URL将有受到 CSRF、XSS、Clickjacking 等攻击的可能。 return (web) -> web.ignoring().requestMatchers("/websocket/**"); } /** * 配置跨源访问(CORS) * @return */ @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.addAllowedOriginPattern("*"); corsConfiguration.addAllowedHeader("*"); corsConfiguration.addAllowedMethod("*"); corsConfiguration.setAllowCredentials(true); source.registerCorsConfiguration("/**", corsConfiguration); return new CorsFilter(source); } }
package com.yw.reviewtest.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.yw.reviewtest.mapper.UserMapper; import com.yw.reviewtest.pojo.User; import com.yw.reviewtest.utils.UserDetailsImpl; import org.springframework.beans.factory.annotation.Autowired; 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; /** * @author yw~ * @version 1.0 */ @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { QueryWrapper<User> queryWrapper = new QueryWrapper(); queryWrapper.eq("username", username); User user = userMapper.selectOne(queryWrapper); if (user == null) { throw new UsernameNotFoundException("用户不存在"); } return new UserDetailsImpl(user); } }
package com.yw.reviewtest.utils; import com.yw.reviewtest.pojo.User; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; /** * @author yw~ * @version 1.0 */ @Data @AllArgsConstructor @NoArgsConstructor public class UserDetailsImpl implements UserDetails { private User user; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return null; } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
因为security默认密码加密为{noop}+密码
在用户进行用户认证登录的时候,前端传来的密码会用BCrpt加密方式进行加密,加密后的密码格式是{id}password,然后程序员会先获取到加密方式也即是{id},假设没有写passwordEncoder(new BCryptPasswordEncoder())那么前端传来的值就不会用BCrpt加密方式进行加密,所以程序员获取前端传来的值时是寻找不到密码中的{id}也即是加密方式的,因此就会报There is no PasswordEcoder mapped for the id "null"异常。
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
- 1
- 2
- 3
- 4
jwt:
第一次前端登录,调取后端接口,发送账号和密码,
后端接收到判断和数据库信息是否相同,相同则根据用户或者用户id生成一个jwt返回给前端,
前端接收到jwt存储到本地并跳转相应页面
前端每次跳转页面时都会携带该jwt发送给后端,后端都将对该jwt进行解析
解析成对应用户id来获得相应权限,jwt验证成功就返回给前端数据
如果jwt解析过期或者失败,就返回前端401,前端接收到就清除jwt然后返回登录页面
这里就可以使用security自带登录对数据库信息进行验证登录了
通过该工具类根据用户id生成jwt
//将userId封装成JWTtoken String jwt= JwtUtil.createJWT(user.getId().toString());
- 1
- 2
// utils.JwtUtil //为jwt工具类,用来创建、解析 jwt_token //作用是将我们的字符串加上密钥加上有效期变成一个加密后的字符串 //另外一个作用是给我们一个令牌让我们把她的有效期解析出来 package com.yw.reviewtest.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.stereotype.Component; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; import java.util.Date; import java.util.UUID; @Component public class JwtUtil { // 有效期14天 public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14; // 密钥,内容是随机字符串,长度必须足够长,只能是大小写英文和数字 public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232131afasdfac"; // public static String getUUID() { return UUID.randomUUID().toString().replaceAll("-", ""); } public static String createJWT(String subject) { JwtBuilder builder = getJwtBuilder(subject, null, getUUID()); return builder.compact(); } private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; SecretKey secretKey = generalKey(); long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); if (ttlMillis == null) { ttlMillis = JwtUtil.JWT_TTL; } long expMillis = nowMillis + ttlMillis; Date expDate = new Date(expMillis); return Jwts.builder() .setId(uuid) .setSubject(subject) .setIssuer("sg") .setIssuedAt(now) .signWith(signatureAlgorithm, secretKey) .setExpiration(expDate); } public static SecretKey generalKey() { byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY); return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256"); } public static Claims parseJWT(String jwt) throws Exception { SecretKey secretKey = generalKey(); return Jwts.parserBuilder() .setSigningKey(secretKey) .build() .parseClaimsJws(jwt) .getBody(); } }
客户端的请求头里携带了 token,服务端肯定是需要针对每次请求解析校验 token 的,所以必须得定义一个过滤器,也就是 JwtAuthenticationTokenFilter:
- 从请求头中获取 token
- 对 token 进行解析、验签、校验过期时间
- 校验成功,将验证结果放到 ThreadLocal 中,供下次请求使用
package com.yw.reviewtest.config; import com.yw.reviewtest.mapper.UserMapper; import com.yw.reviewtest.pojo.User; import com.yw.reviewtest.utils.JwtUtil; import com.yw.reviewtest.utils.UserDetailsImpl; import io.jsonwebtoken.Claims; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; /** * @author yw~ * @version 1.0 */ @Component public class JwtAuthenticationConfig extends OncePerRequestFilter { @Autowired private UserMapper userMapper; @Override protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException { String token = request.getHeader("Authorization"); if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) { filterChain.doFilter(request, response); return; } token = token.substring(7); String userid; try { Claims claims = JwtUtil.parseJWT(token); userid = claims.getSubject(); } catch (Exception e) { throw new RuntimeException(e); } User user = userMapper.selectById(Integer.parseInt(userid)); if (user == null) { throw new RuntimeException("用户名未登录"); } UserDetailsImpl loginUser = new UserDetailsImpl(user); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null); SecurityContextHolder.getContext().setAuthentication(authenticationToken); filterChain.doFilter(request, response); } }
这里使用在Securitty里面配置了
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。