# spring-security
**Repository Path**: liang-tian-yu/spring-security
## Basic Information
- **Project Name**: spring-security
- **Description**: spring-security权限管理
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2025-06-23
- **Last Updated**: 2025-07-07
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## SpringSecurity
[参考文章](https://juejin.cn/post/7400195628847382564)
说明:使用SpringSecurity + jjwt 实现
- 导入依赖
```
org.springframework.boot
spring-boot-starter-security
io.jsonwebtoken
jjwt
0.9.1
```
- application配置
```
jwt:
# token过期时间,1个小时
expiration: 3600000
# token在header中的属性名
tokenHeader: Authorization
# 生成token的密钥
secret: jwt-token-secret
```
### 代码实现
- JwtTokenUtil(创建`jwt`工具类,方便实现根据用户信息生成`token`,以及通过`token`中获取用户信息)
```
package com.lty.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Clock;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.DefaultClock;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* jwt工具类
*/
@Component
@Data
public class JwtTokenUtil implements Serializable {
private static final long serialVersionUID = -3301605591108950415L;
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
private Clock clock = DefaultClock.INSTANCE;
// 根据用户信息生成token
public String generateToken(UserDetails userDetails) {
Map claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
private String doGenerateToken(Map claims, String subject) {
final Date createdDate = clock.now();
final Date expirationDate = calculateExpirationDate(createdDate);
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(createdDate)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
private Date calculateExpirationDate(Date createdDate) {
return new Date(createdDate.getTime() + expiration);
}
public Boolean validateToken(String token, UserDetails userDetails) {
SecurityUserDetails user = (SecurityUserDetails) userDetails;
final String username = getUsernameFromToken(token);
return (username.equals(user.getUsername())
&& !isTokenExpired(token)
);
}
// 通过token获取用户名username
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public T getClaimFromToken(String token, Function claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(clock.now());
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
}
```
- 用户信息
```
package com.lty.security.model;
import lombok.Data;
@Data
public class SysUser {
private Integer id;
private String username;
private String password;
}
```
```
package com.lty.security;
import com.lty.security.model.SysUser;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import java.util.Collection;
/**
* 自定义用户信息
*/
@Data
@EqualsAndHashCode
@Accessors(chain = true) // 实现链式set方法
public class SecurityUserDetails extends SysUser implements UserDetails {
// 权限列表
private Collection extends GrantedAuthority> authorities;
public SecurityUserDetails(String userName, Collection extends GrantedAuthority> authorities) {
// TODO 这里可以从数据库中查询用户信息
this.setUsername(userName);
String encode = new BCryptPasswordEncoder().encode("123456");
this.setPassword(encode);
this.setAuthorities(authorities);
}
/**
* 下面这些都返回true
*
* @return
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
```
- 重写`UserDetailsService`的`loadUserByUsername`方法实现具体的认证授权逻辑
```
package com.lty.security;
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 java.util.ArrayList;
import java.util.List;
@Service
public class JwtUserDetailsServiceImpl implements UserDetailsService {
/**
* 实现具体的认证授权逻辑
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List authorityList = new ArrayList<>();
// TODO 这里直接把用户的权限写死,ROLE_USER表示用户拥有USER权限,因为权限都是以ROLE_开头的。
authorityList.add(new SimpleGrantedAuthority("ROLE_USER"));
return new SecurityUserDetails(username,authorityList);
}
}
```
- 建一个用户请求的过滤器,用来拦截用户请求,分析用户有没有该请求的权限
```
package com.lty.security;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
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;
/**
* 拦截用户请求(拦截用户请求,分析用户有没有该请求的权限)
*/
@Component
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
private final UserDetailsService userDetailsService;
private final JwtTokenUtil jwtTokenUtil;
private final String tokenHeader;
public JwtAuthorizationTokenFilter(@Qualifier("jwtUserDetailsServiceImpl") UserDetailsService userDetailsService,
JwtTokenUtil jwtTokenUtil,
@Value("${jwt.tokenHeader}") String tokenHeader) {
this.userDetailsService = userDetailsService;
this.jwtTokenUtil = jwtTokenUtil;
this.tokenHeader = tokenHeader;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
final String requestHeader = request.getHeader(this.tokenHeader);
String username = null;
String authToken = null;
if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
authToken = requestHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
} catch (ExpiredJwtException e) {
e.printStackTrace();
}
}
// 验证用户身份
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(request, response);
}
}
```
- 实现`AuthenticationEntryPoint`接口的`commence`方法,当请求没有携带认证信息或者说认证失败时,使用我们自己编写的处理逻辑
```
package com.lty.security;
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 JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
// TODO 自定义的错误逻辑
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"code\":401,\"message\":\"未授权,请登录\"}"); // 返回JSON格式的错误信息
}
}
```
- `Security`的核心配置类,重写`WebSecurityConfigurerAdapter`中的`configure`方法
```
package com.lty.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
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.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
JwtUserDetailsServiceImpl jwtUserDetailsService;
@Autowired
JwtAuthorizationTokenFilter authenticationTokenFilter;
@Autowired
@Lazy
PasswordEncoder passwordEncoder;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint) // 使用自定义的认证失败方法
.and()
.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers(HttpMethod.OPTIONS, "/**").anonymous()
.anyRequest().authenticated() // 除上面以外的都拦截
.and()
.csrf().disable() // 禁用security自带的跨域处理
// 让Security不使用session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
public void configure(WebSecurity web) {
// 配置静态文件不需要认证
web.ignoring().antMatchers("/css/**");
web.ignoring().antMatchers("/js/**");
}
/**
* 指定加密方式
*/
@Bean
public PasswordEncoder passwordEncoderBean() {
return new BCryptPasswordEncoder();
}
/**
* 认证逻辑配置
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder);
}
}
```
### 测试
```
package com.lty.controller;
import com.lty.security.JwtTokenUtil;
import com.lty.security.model.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.access.prepost.PreAuthorize;
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.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
public class LoginController {
@Autowired
@Qualifier("jwtUserDetailsServiceImpl")
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@PostMapping("/login")
public String login(@RequestBody SysUser sysUser, HttpServletRequest request){
final UserDetails userDetails = userDetailsService.loadUserByUsername(sysUser.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return token;
}
@PreAuthorize("hasAnyRole('USER')")
@PostMapping(value = "/testUser")
public String testNeed() {
// 测试用户权限
return "hello world";
}
@PostMapping(value = "/info")
public SysUser info() {
// 获取当前登录用户信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
SysUser user = null;
if (authentication != null && authentication.isAuthenticated()) {
user = ((SysUser) authentication.getPrincipal());
}
return user;
}
}
```
- 接口测试
登陆接口
```
curl --location --request POST 'http://localhost:8088/api/login' \
--header 'Content-Type: application/json' \
--data-raw '{
"username":"test"
}'
```
权限认证
```
curl --location --request POST 'http://localhost:8088/api/testUser' \
--header 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0IiwiZXhwIjoxNzUwNjUwNzQzLCJpYXQiOjE3NTA2NDcxNDN9.e3FXSx2MVPe7CEUmLjWWqb-3VXntO5VrvTsCsNACa7NAQOQ8xqidJWDUhsf0vzQW2oZO0CB8RE_Jvqfjzalj4g' \
--header 'Cookie: JSESSIONID=81F5FE1594C0C236FCDE21E866C6BA6B'
```
用户信息
```
curl --location --request POST 'http://localhost:8088/api/info' \
--header 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0IiwiZXhwIjoxNzUwNjUwNzQzLCJpYXQiOjE3NTA2NDcxNDN9.e3FXSx2MVPe7CEUmLjWWqb-3VXntO5VrvTsCsNACa7NAQOQ8xqidJWDUhsf0vzQW2oZO0CB8RE_Jvqfjzalj4g' \
--header 'Cookie: JSESSIONID=81F5FE1594C0C236FCDE21E866C6BA6B; JSESSIONID=81F5FE1594C0C236FCDE21E866C6BA6B'
```