准备
依赖
xml
<dependencies>
<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>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.13</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
</dependencies>
用户实体类
java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String username;
private String password;
}
controller接口
Java
@RestController
@RequestMapping("/user")
public class controller {
@Autowired
private LoginService loginService;
@PostMapping("/login")
public String login(@RequestBody User userLogin){
String token = loginService.login(userLogin);
return token;
}
@PostMapping("/hello")
@PreAuthorize("hasAuthority('user')")//这里添加访问该接口需要的角色为 'user'
public String hello(){
return "Hello";
}
}
集成
首先实现两个接口
- UserDetailsService--springsecurity默认有认证的账号密码,我们不使用它自带的,实现这个接口重写它的方法用于获取我们真正的用户数据
java
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (username == null){//用户不存在的处理
throw new UsernameNotFoundException("用户不存在");
}
//这里可以访问数据库获取用户信息
User user=new User();
user.setUsername("admin");
//这里的密码是admin使用BCryptPasswordEncoder加密后的结果,数据库中存储的一定是加密后的密码
user.setPassword("$2a$10$iLLMvl6E9t7qDYexmu65VOM.tVgGwp5wHh5hSG4aC7rYuRoA6I//m");
//用户权限列表,假设用户有一个user角色
List<String> list =new ArrayList<String>(Arrays.asList("user"));
//封装一个loginUser对象,也是springsecurity内置对象的实现类
LoginUser loginUser = new LoginUser(user,list);
return loginUser;
}
}
- UserDetails--spring认证需要的对象,实现这个方法加入我们自己的逻辑
java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails {
private User user;//我们自己的用户对象
private List<String> permissions;//权限列表
@JSONField(serialize = false)//一定要加,否则集成redis运行会出问题
private List<SimpleGrantedAuthority> authorities;//提取出来的字段,提高效率
//构造器--自定义
public LoginUser(User user, List<String> permissions) {
this.user = user;
this.permissions = permissions;
}
@Override
//权限集合获取的方法,这个方法的逻辑也可以改写,如果User中有权限集合可以从User中获取
public Collection<? extends GrantedAuthority> getAuthorities () {
if (authorities == null)//如果已经有这个集合,我们就不用再赋值一次,直接使用当前的authorities
authorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
return authorities;
}
@Override
//获取用户密码的方法
public String getPassword () {
return user.getPassword();
}
@Override
//获取用户名
public String getUsername () {
return user.getUsername();
}
//以下四个方法返回值都为true即可
@Override
public boolean isAccountNonExpired () {
return true;
}
@Override
public boolean isAccountNonLocked () {
return true;
}
@Override
public boolean isCredentialsNonExpired () {
return true;
}
@Override
public boolean isEnabled () {
return true;
}
}
进行配置
java
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法级别的安全控制
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private JwtFilter jwtFilter; // 过滤器链对象,用于认证和鉴权
@Bean
public PasswordEncoder passwordEncoder() {
// 使用 BCrypt 加密算法进行密码加密与比对
return new BCryptPasswordEncoder();
}
// 注入 AuthenticationManager 对象,用于调用认证方法
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean(); // 返回 AuthenticationManager 实例
}
// 配置请求的权限控制
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 关闭 CSRF 保护
.csrf().disable()
// 不通过 Session 获取 SecurityContext,设置为无状态
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// 配置请求的授权规则
.authorizeRequests()
// 对于登录接口允许匿名访问
.antMatchers("/user/login").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
// 在 UsernamePasswordAuthenticationFilter 前添加 JWT 过滤器
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
}
过滤器
java
@Component
public class JwtFilter extends OncePerRequestFilter {//实现这个接口用于每个请求只会被处理一次
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 从请求头中获取 token
String token = request.getHeader("token");
// 如果没有 token,继续执行后续的过滤器(例如登录流程)
if (StrUtil.isBlank(token)) {
filterChain.doFilter(request, response);
return;
}
// 从 token 中提取用户信息
// 这里可以通过 JWT 工具类获取 token 中的用户信息,或从 Redis 中获取用户信息
// 为演示目的,直接实例化一个用户对象
User user = new User();
user.setUsername("admin"); // 设置用户名
user.setPassword("$2a$10$iLLMvl6E9t7qDYexmu65VOM.tVgGwp5wHh5hSG4aC7rYuRoA6I//m"); // 设置密码(已加密)
// 创建一个权限列表,并添加权限
List<String> list = new ArrayList<>();
list.add("user");
// 创建 LoginUser 对象,包含用户信息和权限
LoginUser loginUser = new LoginUser(user, list);
// 登录成功后,使用 UsernamePasswordAuthenticationToken 存储用户信息和权限信息
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
// 将用户信息存入 SecurityContext,后续可以从这里取出用户信息
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
// 继续执行后续的过滤器链
filterChain.doFilter(request, response);
}
}
在LoginService实现类中进行认证
java
@Service
public class LoginService {
@Resource
private AuthenticationManager manager; // 注入 AuthenticationManager,用于用户认证
public String login(User user) {
// 创建 UsernamePasswordAuthenticationToken,用于认证
UsernamePasswordAuthenticationToken userAuthentication =
new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
// 调用 AuthenticationManager 的方法进行用户认证
Authentication authenticate = manager.authenticate(userAuthentication);
// 如果认证成功(authenticate 不为空),进入生成 token 的逻辑
if (!Objects.isNull(authenticate)) {
// 获取 LoginUser 对象
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
// 获取真正的 User 对象
User u = loginUser.getUser();
// 生成 token
String token = JWT
.create()
.setPayload("userLoginId", u.getId()) // 设置用户 ID 为负载
.setIssuedAt(new Date()) // 设置 token 的发放时间
.setExpiresAt(new Date(System.currentTimeMillis() + DateUnit.WEEK.getMillis())) // 设置过期时间为一周
.setSigner("HMD5", "salt".getBytes(StandardCharsets.UTF_8)) // 设置加密算法和盐值
.sign(); // 生成并签名 token
// 返回生成的 token
return token;
}
// 如果认证失败,抛出异常
throw new RuntimeException("用户名或密码错误");
}
}
测试

携带刚刚生成的token去访问需要权限的接口
