本文最后更新于:2025年2月18日 下午
Spring-Security
一、配置类方法
1、Maven依赖
1 2 3 4 5 6 7 8 9 10 11 12
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
|
2、配置类
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
| package com.example.config;
import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
@Override protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable(); http.exceptionHandling().accessDeniedPage("/403"); http.authorizeRequests() .antMatchers("/login").permitAll() .anyRequest().authenticated(); http.formLogin() .loginPage("/login") .loginProcessingUrl("/user/login") .defaultSuccessUrl("/") .failureUrl("/logout") .usernameParameter("username") .passwordParameter("password") .successHandler() .failureHandler(); http.logout().logoutUrl("/logout"). logoutSuccessUrl("/login"); }
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); }
@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
|
3、自定义认证类
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
| package com.example.service;
import com.example.domain.User; import com.example.mapper.UserMapper; import lombok.RequiredArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service;
import java.util.ArrayList; import java.util.List;
@Service @RequiredArgsConstructor public class userDetailsServiceImpl implements UserDetailsService {
private final UserMapper userMapper;
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { List<GrantedAuthority> grantedAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin"); User user = new User(); user.setUsername(username); user.setPassword(new BCryptPasswordEncoder().encode("123456"));
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), grantedAuthorities); } }
|
4、用户实体类
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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| package com.example.domain;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection; import java.util.Collections; import java.util.Set; import java.util.stream.Collectors;
@Data @TableName("user") public class User implements UserDetails { @TableId private Long id; private String username; private String password; private String role; private Set<String> roles;
@Override public Collection<? extends GrantedAuthority> getAuthorities() { return roles.stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); }
@Override public boolean isAccountNonExpired() { return true; }
@Override public boolean isAccountNonLocked() { return true; }
@Override public boolean isCredentialsNonExpired() { return true; }
@Override public boolean isEnabled() { return true; } }
|
5、自定义登录后续处理器
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
| package com.example.service;
import com.example.utils.JwtTokenUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; 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 CustomAuthenticationHandler implements AuthenticationSuccessHandler, AuthenticationFailureHandler {
@Autowired private JwtTokenUtil jwtTokenUtil;
@Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { String username = authentication.getName();
String token = jwtTokenUtil.generateToken(username);
response.setContentType("application/json"); response.setStatus(HttpServletResponse.SC_OK); response.getWriter().write("{\"token\": \"" + token + "\"}"); response.getWriter().flush(); }
@Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { } }
|
6、配置文件
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
| server: port: 80 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456 url: jdbc:mysql://127.0.0.1:3306/demo?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true
thymeleaf: enabled: true
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl type-aliases-package: com.example.entity
global-config: db-config: logic-not-delete-value: 0 logic-delete-value: 1 logic-delete-field: deleted
jwt: secret: key expiration: 3600000
|
7、jwt工具类
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
| package com.example.utils;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component;
import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.function.Function;
@Component public class JwtTokenUtil {
@Value("${jwt.secret}") private String secret;
@Value("${jwt.expiration}") private long expiration;
public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); }
public Date extractExpiration(String token) { return extractClaim(token, Claims::getExpiration); }
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) { final Claims claims = extractAllClaims(token); return claimsResolver.apply(claims); }
private Claims extractAllClaims(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); }
private Boolean isTokenExpired(String token) { return extractExpiration(token).before(new Date()); }
public String generateToken(String userDetails) { Map<String, Object> claims = new HashMap<>(); return createToken(claims, userDetails); }
private String createToken(Map<String, Object> claims, String subject) { return Jwts.builder() .setClaims(claims) .setSubject(subject) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expiration)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); }
public Boolean validateToken(String token, UserDetails userDetails) { final String username = extractUsername(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } }
|
二、注解方法
要使用注解,需先开启注解使用,可在启动类或配置类上使用以下注解开启
1
| @EnableGlobalMethodSecurity(securedEnabled = true)
|
完整启动类示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.example;
import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
@MapperScan("com.example.mapper") @EnableGlobalMethodSecurity(securedEnabled = true) @SpringBootApplication public class Main { public static void main(String[] args) { SpringApplication.run(Main.class, args); } }
|
1、@Secured
被此注解修饰下的方法,访问者必须有注解中所指示的角色,否则不允许访问。
角色前要加ROLE_
示例:
1 2 3 4 5 6
| @GetMapping("/") @ResponseBody @Secured({"ROLE_ADMIN","ROLE_USER"}) public String index() { return "index"; }
|
2、@PreAuthorize
此注解修饰下的方法,会在进入方法前验证身份
需要先在开启注解的参数里添加:prePostEnabled = true
启动类完整示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| package com.example;
import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
@MapperScan("com.example.mapper") @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true) @SpringBootApplication public class Main { public static void main(String[] args) { SpringApplication.run(Main.class, args); } }
|
方法上完整示例
1 2 3 4 5 6
| @GetMapping("/") @ResponseBody @PreAuthorize("hasAnyAuthority('ADMIN', 'USER')") public String index() { return "index"; }
|
3、@PostAuthorize
此注解修饰下的方法,会在方法执行后验证身份 >>> 一般用于验证返回值
需要先在开启注解的参数里添加:prePostEnabled = true
完整示例:
1 2 3 4 5 6
| @GetMapping("/") @ResponseBody @PostAuthorize("hasAnyAuthority('ADMIN', 'USER')") public String index() { return "index"; }
|
4、@PreFilter
此注解修饰下的方法,会过滤传入到方法的数据
完整示例:
1 2 3 4 5 6 7
| @GetMapping("/") @ResponseBody @PostAuthorize("hasAnyAuthority('ADMIN', 'USER')") @PreFilter("filterObject.contains('admin')") public String index() { return "index"; }
|
5、@PostFilter
此注解修饰下的方法,会过滤返回的数据
完整示例:filterObject标识任意对象
1 2 3 4 5 6 7 8 9 10 11
| @GetMapping("/") @ResponseBody @PostAuthorize("hasAnyAuthority('admin', 'user')") @PostFilter("filterObject.contains('admin')") public List<String> index() { System.out.println(">>>>>>>"); List<String> list = new ArrayList<>(); list.add("user"); return list; }
|