Spring Security 快速开始
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
一、认证
1、从数据中读数据完成认证
@Service
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws AuthenticationException {
MyUser myUser;
// 这里模拟从数据库中获取用户信息
if (username.equals("admin")) {
myUser = new MyUser("admin", new BCryptPasswordEncoder().encode("123456"), new ArrayList<>(Arrays.asList("p1", "p2")));
return myUser;
} else {
throw new UsernameNotFoundException("用户不存在");
}
}
}
集成User可以补充一些自己的数据,修改构造方法(或者直接使用User)
public class MyUser extends User {
private int sex;
private int age;
private String address;
public MyUser(String username, String password, List<String> authorities) {
super(username, password, authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
相关配置
@Configuration
public class MySecurityConfigurer extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
2、实现同时多种认证方式
在1的基础上添加相关配置
/**
* 手机验证码认证信息,在UsernamePasswordAuthenticationToken的基础上添加属性 手机号、验证码
*/
public class MobilecodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 530L;
private Object principal;
private Object credentials;
private String phone;
private String mobileCode;
public MobilecodeAuthenticationToken(String phone, String mobileCode) {
super(null);
this.phone = phone;
this.mobileCode = mobileCode;
this.setAuthenticated(false);
}
public MobilecodeAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
public Object getCredentials() {
return this.credentials;
}
public Object getPrincipal() {
return this.principal;
}
public String getPhone() {
return phone;
}
public String getMobileCode() {
return mobileCode;
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
} else {
super.setAuthenticated(false);
}
}
public void eraseCredentials() {
super.eraseCredentials();
this.credentials = null;
}
}
public class MobilecodeAuthenticationProvider implements AuthenticationProvider {
private UserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
MobilecodeAuthenticationToken mobilecodeAuthenticationToken = (MobilecodeAuthenticationToken) authentication;
String phone = mobilecodeAuthenticationToken.getPhone();
String mobileCode = mobilecodeAuthenticationToken.getMobileCode();
System.out.println("登陆手机号:" + phone);
System.out.println("手机验证码:" + mobileCode);
// 模拟从redis中读取手机号对应的验证码及其用户名
Map<String,String> dataFromRedis = new HashMap<>();
dataFromRedis.put("code", "6789");
dataFromRedis.put("username", "admin");
// 判断验证码是否一致
if (!mobileCode.equals(dataFromRedis.get("code"))) {
throw new BadCredentialsException("验证码错误");
}
// 如果验证码一致,从数据库中读取该手机号对应的用户信息
MyUser loadedUser = (MyUser) userDetailsService.loadUserByUsername((String)dataFromRedis.get("username"));
if (loadedUser == null) {
throw new UsernameNotFoundException("用户不存在");
} else {
MobilecodeAuthenticationToken result = new MobilecodeAuthenticationToken(loadedUser, null, loadedUser.getAuthorities());
return result;
}
}
@Override
public boolean supports(Class<?> aClass) {
return MobilecodeAuthenticationToken.class.isAssignableFrom(aClass);
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
}
修改配置文件
@Configuration
public class MySecurityConfigurer extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService myUserDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public MobilecodeAuthenticationProvider mobilecodeAuthenticationProvider() {
MobilecodeAuthenticationProvider mobilecodeAuthenticationProvider = new MobilecodeAuthenticationProvider();
mobilecodeAuthenticationProvider.setUserDetailsService(myUserDetailsService);
return mobilecodeAuthenticationProvider;
}
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
daoAuthenticationProvider.setUserDetailsService(myUserDetailsService);
return daoAuthenticationProvider;
}
/**
* 定义认证管理器AuthenticationManager
* @return
*/
@Bean
public AuthenticationManager authenticationManager() {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
authenticationProviders.add(mobilecodeAuthenticationProvider());
authenticationProviders.add(daoAuthenticationProvider());
ProviderManager authenticationManager = new ProviderManager(authenticationProviders);
// authenticationManager.setEraseCredentialsAfterAuthentication(false);
return authenticationManager;
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
// 关闭csrf
.csrf().disable()
// 权限配置,登录相关的请求放行,其余需要认证
.authorizeRequests()
.antMatchers("/login/*").permitAll()
.anyRequest().authenticated();
}
}
发送登录请求
@RestController
@RequestMapping("/login")
public class TestController {
@GetMapping("/hello")
public String hello(){
return "Hello Spring security";
}
@Autowired
private AuthenticationManager authenticationManager;
/**
* 用户名密码登录
* @param username
* @param password
* @return
*/
@GetMapping("/usernamePwd")
public Result usernamePwd(String username, String password) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password);
Authentication authenticate = null;
try {
authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
} catch (Exception e) {
e.printStackTrace();
return Result.error("登陆失败");
}
String token = UUID.randomUUID().toString().replace("-", "");
return Result.ok(token);
}
/**
* 手机验证码登录
* @param phone
* @param mobileCode
* @return
*/
@GetMapping("/mobileCode")
public Result mobileCode(String phone, String mobileCode) {
MobilecodeAuthenticationToken mobilecodeAuthenticationToken = new MobilecodeAuthenticationToken(phone, mobileCode);
Authentication authenticate = null;
try {
authenticate = authenticationManager.authenticate(mobilecodeAuthenticationToken);
} catch (Exception e) {
e.printStackTrace();
return Result.error("验证码错误");
}
String token = UUID.randomUUID().toString().replace("-", "");
return Result.ok(token);
}
}
3、用token 校验过滤
认证成功后通常会返回一个token令牌(如jwt等)
,后续我们将token放到请求头中进行请求,后端校验该token,校验成功后再访问相应的接口
@Component
@WebFilter
public class TokenAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String token = httpServletRequest.getHeader("token");
// 如果没有token,跳过该过滤器
if (!StringUtils.isEmpty(token)) {
// 模拟redis中的数据
Map map = new HashMap();
map.put("test_token1", new MyUser("admin", new BCryptPasswordEncoder().encode("123456"), new ArrayList<>(Arrays.asList("p1", "p2"))));
map.put("test_token2", new MyUser("root", new BCryptPasswordEncoder().encode("123456"), new ArrayList<>(Arrays.asList("p1", "p2"))));
// 这里模拟从redis获取token对应的用户信息
MyUser myUser = (MyUser)map.get(token);
if (myUser != null) {
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(myUser, null, myUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authRequest);
} else {
throw new PreAuthenticatedCredentialsNotFoundException("token不存在");
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
修改配置 MySecurityConfigurer
@Override
public void configure(HttpSecurity http) throws Exception {
http
// 关闭csrf
.csrf().disable()
// 权限配置,登录相关的请求放行,其余需要认证
.authorizeRequests()
.antMatchers("/login/*").permitAll()
.anyRequest().authenticated()
.and()
// 添加token认证过滤器
.addFilterAfter(tokenAuthenticationFilter, LogoutFilter.class)
// 不使用session会话管理
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
二、认证
1、基于方法的授权
当我们想要开启
spring
方法级安全时,只需要在任何@Configuration
实例上使用@EnableGlobalMethodSecurity
注解就能达到此目的。同时这个注解为我们提供了prePostEnabled
、securedEnabled
和jsr250Enabled
三种不同的机制来实现同一种功能。
@GetMapping("/hello")
@PreAuthorize("hasAuthority('cece2')")
public String hello(){
return "Hello Spring security";
}
也可以
@PreAuthorize("@el.verify('cece2')") 然后往容器中添加el 实例,然后使用自定义的方法验证权限
2、基于Request的授权
http.authorizeRequests(
authorize -> authorize
// 添加授权配置
.requestMatchers("/user/list").hasAnyAuthority("USER_LIST")
.requestMatchers("/user/add").hasAnyAuthority("USER_ADD")
// 对所有请求开启授权保护
.anyRequest()
// 已认证的请求会被自动授权
.authenticated()
)
三、相关配置
Security 常用配置-CSDN博客
四:参考
Spring Security入门教程
Spring Security 实现多种认证方式