下面是详细的讲解“Springboot+Spring Security实现前后端分离登录认证及权限控制的示例代码”的攻略。
1. Spring Security简介
Spring Security 是一个强大且高度可定制的身份验证和访问控制框架,与 Spring 应用程序无缝集成,具有广泛的可用插件和扩展点以满足几乎任何身份验证和授权要求。Spring Security 提供的安全功能包括认证、授权、防止 CSRF、会话管理等。
2. Springboot+Spring Security实现前后端分离登录认证及权限控制
下面是Springboot+Spring Security实现前后端分离登录认证及权限控制的步骤。
2.1 修改pom.xml文件
添加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
2.2 编写Spring Security配置类
新建一个类SecurityConfig,使用@EnableWebSecurity注解开启Spring Security支持,并继承WebSecurityConfigurerAdapter类。重写configure方法,设置HttpSecurity配置。示例代码如下:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private AuthenticationEntryPoint unauthorizedHandler;
@Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(this.userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// 禁用 CSRF
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// 不创建会话
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
// 允许匿名访问的URL
.antMatchers(
HttpMethod.GET,
"/",
"/v2/api-docs", // swagger api json
"/swagger-resources/**", // swagger 资源
"/swagger-ui.html",
"/webjars/**"
).permitAll()
// 所有请求需要身份认证
.anyRequest().authenticated();
// 添加JWT过滤器
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter();
jwtAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
httpSecurity.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
//添加自定义的AccessDecisionManager
httpSecurity.authorizeRequests().accessDecisionManager(updateAccessDecisionManager());
}
private AccessDecisionManager updateAccessDecisionManager() {
List<AccessDecisionVoter<? extends Object>> decisionVoters
= Arrays.asList(new RoleVoter(), new AuthenticatedVoter());
return new AffirmativeBased(decisionVoters);
}
}
2.3 编写Spring Security过滤器
新建一个类JwtAuthenticationFilter,继承OncePerRequestFilter类,实现JSON Web Token(JWT)的认证。具体实现步骤如下:
- 从请求头获取JWT,判断是否存在。
- 如果JWT存在,使用JWT解码出载荷中的用户名。
- 从数据库查询该用户名对应的用户对象。
- 构造一个Token对象(UsernamePasswordAuthenticationToken)。
- 将Token传入SecurityContextHolder中。
示例代码如下:
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenProvider jwtTokenProvider;
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(httpServletRequest);
if (StringUtils.hasText(jwt) && jwtTokenProvider.validateToken(jwt)) {
String username = jwtTokenProvider.getUsernameFromJWT(jwt);
UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
logger.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader(JwtTokenProvider.AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(JwtTokenProvider.TOKEN_PREFIX)) {
return bearerToken.substring(JwtTokenProvider.TOKEN_PREFIX.length());
}
return null;
}
}
2.4 编写JWT认证的Provider
新建一个类JwtTokenProvider,实现JWT的生成和解码。具体实现步骤如下:
- 使用JWT的默认算法HMAC256,使用自定义加密的KEY。
- 生成JWT时,使用当前的时间和有效期作为payload。
- 解码JWT时,使用JWT的包含的信息对JWT进行解码。
示例代码如下:
@Component
public class JwtTokenProvider {
public static final String AUTHORIZATION_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
private final String secretKey = "mySecretKey";
public String generateToken(Authentication authentication) {
CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal();
Date now = new Date();
Date expiryDate = new Date(now.getTime() + 3600 * 1000);
return Jwts.builder()
.setSubject(customUserDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, secretKey)
.compact();
}
public String getUsernameFromJWT(String token) {
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
public boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(authToken);
return true;
} catch (SignatureException ex) {
logger.error("Invalid JWT signature");
} catch (MalformedJwtException ex) {
logger.error("Invalid JWT token");
} catch (ExpiredJwtException ex) {
logger.error("Expired JWT token");
} catch (UnsupportedJwtException ex) {
logger.error("Unsupported JWT token");
} catch (IllegalArgumentException ex) {
logger.error("JWT claims string is empty.");
}
return false;
}
}
2.5 编写自定义UserDetailsService类
新建一个类CustomUserDetailsService,实现Spring Security的UserDetailsService接口。具体实现步骤如下:
- 从数据库获取用户信息(适配自己的数据库)。
- 如果查询到了用户信息,则构造一个Spring Security的UserDetails对象。
示例代码如下:
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
@Transactional
public UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException {
User user = userRepository.findByUsernameOrEmail(usernameOrEmail, usernameOrEmail)
.orElseThrow(() ->
new UsernameNotFoundException("User not found with username or email : " + usernameOrEmail)
);
return CustomUserDetails.create(user);
}
}
2.6 编写Controller
新建一个类UserController,实现Spring MVC的Controller。示例代码如下:
@RestController
@RequestMapping("/api/v1/user")
public class UserController {
@PreAuthorize("hasAnyRole('ADMIN', 'USER')")
@GetMapping("/info")
public String getUserInfo(@AuthenticationPrincipal CustomUserDetails customUserDetails) {
return "Hello, " + customUserDetails.getUsername();
}
}
3. 示例
3.1 登录认证
前端使用POST请求 /login 接口,将用户名和密码发送到后端。后端使用Spring Security的实现类进行认证,认证成功后生成JWT,并将JWT发送到前端。
@RestController
@RequestMapping("/api/v1/auth")
public class AuthController {
@Autowired
private JwtTokenProvider jwtTokenProvider;
@Autowired
private AuthenticationManager authenticationManager;
@PostMapping("/login")
public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsernameOrEmail(),
loginRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtTokenProvider.generateToken(authentication);
return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
}
}
3.2 访问控制
在Controller中使用@PreAuthorize注解控制方法的访问权限。示例代码如下:
@RestController
@RequestMapping("/api/v1/user")
public class UserController {
@PreAuthorize("hasAnyRole('ADMIN', 'USER')")
@GetMapping("/info")
public String getUserInfo(@AuthenticationPrincipal CustomUserDetails customUserDetails) {
return "Hello, " + customUserDetails.getUsername();
}
}
在loadUserByUsername方法中将查询到的用户信息封装成Spring Security的UserDetails对象,并构造了一个CustomUserDetails对象。示例代码如下:
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
@Transactional
public UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException {
User user = userRepository.findByUsernameOrEmail(usernameOrEmail, usernameOrEmail)
.orElseThrow(() ->
new UsernameNotFoundException("User not found with username or email : " + usernameOrEmail)
);
return CustomUserDetails.create(user);
}
}
4. 总结
本文详细讲解了如何使用Springboot+Spring Security实现前后端分离登录认证及权限控制,并提供了示例代码。在实践中,需要根据自己的具体需求和业务逻辑进行调整和实现。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Springboot+Spring Security实现前后端分离登录认证及权限控制的示例代码 - Python技术站