Spring Security使用指南
文章讲解了Spring Security的各个模块和功能。其中包括用户认证、角色授权、密码加密和解密等。文章还提供了详细的代码示例,帮助读者快速上手并理解Spring Security的使用。总之,本文是一篇全面而实用的Spring Security使用指南,对于想要学习和使用Spring Security的开发者来说是一份非常有价值的资料。
一、入门使用
1,引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2,访问请求
假设有如下的接口,当访问时会自动下载权限框架的登录页请求
3,自定义登录页
准备自定义的静态资源
添加配置
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// 配置表单登录
http.formLogin()
// 指定自定义的登录页面 URL
.loginPage("/login.html")
// 指定登录请求的处理 URL
.loginProcessingUrl("/login")
// 允许所有用户访问登录页面和登录处理 URL
.permitAll()
.and()
// 配置授权请求
.authorizeRequests()
// 允许所有用户访问以下路径
.antMatchers( "/css/**", "/js/**", "/images/**")
.permitAll()
// 对于其他所有请求,要求用户必须经过身份验证
.anyRequest()
.authenticated()
.and()
// 禁用 CSRF 保护
.csrf()
.disable();
// 构建并返回安全过滤链
return http.build();
}
}
呈现效果
二、自定义认证
1,基于内存的认证
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Bean
public UserDetailsService users() {
UserDetails user = User.builder()
.username("user")
.password("123456")
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password("112233")
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
2,BCrypt密码加密
BCrypt就是一款加密工具,可以比较方便地实现数据的加密工作。也可以简单理解为它内部自己实现了随机加盐处理。例如,使用MD5加密,每次加密后的密文其实都是一样的,这样就方便了MD5通过大数据的方式进行破解。BCrypt生成的密文长度是60,而MD5的长度是32。
import org.springframework.security.crypto.bcrypt.BCrypt;
public class Main {
public static void main(String[] args) {
byte[] bytes = "123456".getBytes();
System.out.println(bytes);
String hashpw = BCrypt.hashpw(bytes, BCrypt.gensalt());
System.out.println("password-one:" + hashpw);
String hash = BCrypt.hashpw(bytes, BCrypt.gensalt());
System.out.println("password-two:" + hash);
System.out.println("------------------------");
System.out.println(BCrypt.checkpw(bytes, hash));
System.out.println(BCrypt.checkpw(bytes, hashpw));
System.out.println(BCrypt.checkpw("123456", hashpw));
}
}
三、基于JDBC数据库实现认证
1,执行流程
2,基于内存代码执行
在Spring Security框架中提供了一个UserDetailsService 接口,它的主要作用是提供用户详细信息。具体来说,当用户尝试进行身份验证时,UserDetailsService 会被调用,以获取与用户相关的详细信息。这些详细信息包括用户的用户名、密码、角色等
import org.springframework.security.core.userdetails.User;
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.Component;
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (username.equals("user")) {
return User.builder()
.username(username)
.password("$2a$10$mPMldGTPYijVDItlA3/SsuAlv8OK2Rfz7VNbgdU4peyDCsxRqS95y")
.roles("USER")
.build();
}
if (username.equals("admin")) {
return User.builder()
.username(username)
.password("$2a$10$mPMldGTPYijVDItlA3/SsuAlv8OK2Rfz7VNbgdU4peyDCsxRqS95y")
.roles("USER", "ADMIN")
.build();
}
return null;
}
}
3,基于数据库代码执行
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 根据用户名查询用户
User user = userMapper.findByUsername(username);
Assert.isTrue(user != null, "用户不存在");
// 建立角色
SimpleGrantedAuthority userRole = new SimpleGrantedAuthority("user");
SimpleGrantedAuthority adminRole = new SimpleGrantedAuthority("admin");
List<SimpleGrantedAuthority> roleList = new ArrayList<>();
roleList.add(userRole);
roleList.add(adminRole);
return new org.springframework.security.core.userdetails.User(user.getUsername(),
user.getPassword(), roleList);
}
}
4,自定义框架的USER类
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Data
public class UserAuth implements UserDetails {
private String username; //固定不可更改
private String password;//固定不可更改
private String nickName; //扩展属性 昵称
private List<String> roles; //角色列表
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
if (roles == null) return null;
return roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.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;
}
}
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 根据用户名查询用户
User user = userMapper.findByUsername(username);
Assert.isTrue(user != null, "用户不存在");
UserAuth userAuth = new UserAuth();
BeanUtils.copyProperties(user, userAuth);
// 建立角色
List<String> roles=new ArrayList<>();
if ("user@qq.com".equals(user.getUsername())) {
roles.add("USER");
}
if ("admin@qq.com".equals(user.getUsername())) {
roles.add("USER");
roles.add("ADMIN");
}
userAuth.setRoles(roles);
return userAuth;
}
}
// 获取认证对象主体
UserAuth userAuth = (UserAuth) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
四、授权
1,简单WEB授权
新增两个接口
分配角色
当使用user角色访问/admin时,便会返回403权限不足
2,控制操作方法
● permitAll() 方法,所有用户可访问。
● denyAll() 方法,所有用户不可访问。
● authenticated() 方法,登录用户可访问。
● anonymous() 方法,匿名用户可访问。
● rememberMe() 方法,通过 remember me 登录的用户可访问。
● fullyAuthenticated() 方法,非 remember me 登录的用户可访问。
● hasIpAddress(String ipaddressExpression) 方法,来自指定 IP 表达式的用户可访问。
● hasRole(String role) 方法, 拥有指定角色的用户可访问,传入的角色将被自动增加 “ROLE_” 前缀。
● hasAnyRole(String... roles) 方法,拥有指定任意角色的用户可访问。传入的角色将被自动增加 “ROLE_” 前缀。
● hasAuthority(String authority) 方法,拥有指定权限( authority )的用户可访问。
● hasAnyAuthority(String... authorities) 方法,拥有指定任意权限( authority )的用户可访问。
5、整合JWT
1,实现流程
2,注册认证管理
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/security/login").permitAll();
http.csrf().disable();
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService users() {
UserDetails user = User.builder()
.username("user")
.password("$2a$10$2VCyByZ5oeiXCEN73wvBB.xpmJgPBbZVS/Aallmdyij2G7hmAKQTG")
.roles("USER")
.build();
UserDetails admin = User.builder()
.username("admin")
.password("$2a$10$cRH8iMMh6XO0T.ssZ/8qVOo8ThWs/qfntIH3a7yfpbPd05h9ZGx8y")
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
}
3,登录接口
@PostMapping("/login")
public String login(@RequestBody LoginDto loginDto){
// 使用接收到的用户名和密码创建一个认证令牌对象。
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());
// 调用Spring Security的认证管理器来验证提供的认证令牌。
// 如果用户名或密码不正确,这一步会抛出异常。
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
if (authenticate.isAuthenticated()) {
// 获取认证成功的主体
Object principal = authenticate.getPrincipal();
Map<String, Object> claims = new HashMap<>();
claims.put("user", principal);
String token = JWTUtil.createToken(claims, JWT_KEY.getBytes());
log.info("token: {}", token);
return token;
}
return "";
}
4,自定义认证管理
@Component
public class TokenAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication,
RequestAuthorizationContext requestAuthorizationContext) {
HttpServletRequest request = requestAuthorizationContext.getRequest();
String token = request.getHeader("token");
if (!StringUtils.hasText(token)) {
return new AuthorizationDecision(false);
}
Claims claims = JwtUtil.parseJWT(JWT_KEY, token);
//获取userAuth
UserAuth user = JSONObject.parseObject(JSON.toJSONString(claims.get("user")),UserAuth.class);
//存入上下文
UsernamePasswordAuthenticationToken auth
=new UsernamePasswordAuthenticationToken( userAuth, userAuth.getPassword(), userAuth.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
if (user != null) {
return new AuthorizationDecision(false);
}
String requestURI = request.getRequestURI();
if (user.getRoles().contains("admin")) {
if("/hello/admin".equals(requestURI)){
return new AuthorizationDecision(true);
}
}
if(user.getRoles().contains("USER")){
if("/hello/user".equals(requestURI)){
return new AuthorizationDecision(true);
}
}
return new AuthorizationDecision(false);
}
}
@Autowired
private TokenAuthorizationManager tokenAuthorizationManager;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests().antMatchers("/security/login").permitAll()
.anyRequest().access(tokenAuthorizationManager);
http.csrf().disable();
//关闭session
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
//关闭缓存
http.headers().cacheControl().disable();
return http.build();
}