Ruoyi-Vue使用Spring Security和JWT(JSON Web Token)实现了一套基于角色的权限管理和认证授权系统。下面将详细讲解这一实现过程。
- 依赖引入:
在后端的pom.xml
文件中,引入spring-security
和jjwt
相关的依赖。
<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.配置Spring Security:
在后端项目中创建一个名为WebSecurityConfig
的配置类,继承WebSecurityConfigurerAdapter
,并添加以下配置:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// 省略其他配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/login", "/register", "/public/**").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
http.headers().cacheControl();
}
}
这段代码配置了允许无需登录即可访问的URL(如:登录、注册等),以及需要登录才能访问的其他URL。它还禁用了CSRF(跨站请求伪造)保护,添加了自定义的JWT认证过滤器,并设置了无状态的会话策略。
JWT工具类:
创建一个名为JwtTokenUtil
的工具类,用于生成和解析JWT。这个类包含了如下方法:
generateToken(UserDetails userDetails)
:根据用户信息生成JWTgetUsernameFromToken(String token)
:从JWT中获取用户名validateToken(String token, UserDetails userDetails)
:- 验证JWT是否有效用户认证和授权:
创建一个名为JwtUserDetailsService
的服务类,实现UserDetailsService
接口,实现loadUserByUsername
方法。这个方法通过用户名查询数据库获取用户信息,并将用户信息封装成JwtUser
对象。
@Service
public class JwtUserDetailsService implements UserDetailsService {
@Autowired
private SysUserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser user = userService.selectUserByLoginName(username);
if (user == null) {
throw new UsernameNotFoundException("User not found with username: " + username);
}
return createJwtUser(user);
}
}
JWT过滤器:
创建一个名为JwtAuthenticationTokenFilter
的过滤器,继承OncePerRequestFilter
。在doFilterInternal
方法中,从请求头中获取JWT,然后使用JwtTokenUtil
工具类解析JWT,获取用户名。接着,调用`JwtUserDetailsService
加载用户信息,并将其设置到SecurityContextHolder
中,以便后续的认证授权操作。请注意,仅当请求头中包含有效的JWT时,此过滤器才会执行这些操作。
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private JwtUserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String authToken = request.getHeader("Authorization");
if (authToken != null && authToken.startsWith("Bearer ")) {
authToken = authToken.substring(7);
}
String username = jwtTokenUtil.getUsernameFromToken(authToken);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
}
登录控制器:
创建一个名为AuthController
的控制器,实现登录功能。在登录方法中,首先调用AuthenticationManager
进行身份认证,然后使用JwtTokenUtil
生成JWT,并将其作为响应数据返回给前端。
@RestController
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private JwtUserDetailsService userDetailsService;
@PostMapping("/login")
public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtAuthenticationRequest authenticationRequest) {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtAuthenticationResponse(token));
}
private void authenticate(String username, String password) {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (Exception e) {
throw new AuthenticationServiceException("Invalid username or password.", e);
}
}
}
- 前端实现
在前端项目中,使用axios
库实现与后端的通信。首先,在登录页面上收集用户名和密码,然后通过axios
向后端发送POST请求,将用户名和密码作为请求数据。如果登录成功,将返回的JWT存储在浏览器的localStorage
中,并将其添加到后续请求的Authorization
头中。
// 发送登录请求
axios.post('/login', {
username: this.username,
password: this.password
}).then(response => {
localStorage.setItem('token', response.data.token);
// 添加到请求头
axios.defaults.headers.common['Authorization'] = 'Bearer ' + response.data.token;
// 跳转到其他页面
});
- 基于角色的权限管理:在
JwtUserDetailsService
中,从数据库中获取用户的角色信息,并将其封装到JwtUser
对象中。这些角色信息会存储在生成的JWT中,用于后续的权限检查。在配置类WebSecurityConfig
中,通过添加@EnableGlobalMethodSecurity(prePostEnabled = true)
注解来启用基于角色的权限管理。这样,在控制器类中,你可以使用@PreAuthorize
注解来限制某个方法只能被具有特定角色的用户访问。例如:
@RestController
@RequestMapping("/api/users")
public class UserController {
@PreAuthorize("hasRole('ROLE_ADMIN')")
@GetMapping("/all")
public ResponseEntity<List<User>> getAllUsers() {
// ...
}
}
- JWT刷新:
JWT的有效期是有限的,为了避免用户在登录后不久就需要重新登录,你可以实现JWT的刷新机制。在AuthController
中,添加一个用于刷新JWT的接口:
@GetMapping("/refresh")
public ResponseEntity<?> refreshAndGetAuthenticationToken(HttpServletRequest request) {
String authToken = request.getHeader("Authorization");
if (authToken != null && authToken.startsWith("Bearer ")) {
authToken = authToken.substring(7);
}
String refreshedToken = jwtTokenUtil.refreshToken(authToken);
if (refreshedToken == null) {
return ResponseEntity.badRequest().body(null);
} else {
return ResponseEntity.ok(new JwtAuthenticationResponse(refreshedToken));
}
}
前端在发现JWT即将过期时,可以调用这个接口来获取一个新的JWT。当然,你需要在JwtTokenUtil
中实现refreshToken
方法,用于生成新的JWT。