SpringSecurity
SpringSecurity
默认拥有 /login接口和/logout接口
设计 API 授权,或者调用第三方 API 时,经常会接触到:
Authorization : Bearer Tokenxxxxxx
有没有疑惑为何不直接写成这样就得了:
Authorization : Tokenxxxxxx
这是因为 W3C 的 HTTP 1.0 规范,
Authorization 的格式是Authorization: <type> <authorization-parameters>
Bearer 是授权的类型,
常见的授权类型还有:
– Basic 用于 http-basic 认证;
– Bearer 常见于 OAuth 和 JWT 授权;
– Digest MD5 哈希的 http-basic 认证 (已弃用)
– AWS4-HMAC-SHA256 AWS 授权
.......
spring security 的 配置类
package com.example.demo.config;
import com.example.demo.filter.AccountVerifyFilter;
import com.example.demo.filter.CaptchaVerifyFilter;
import com.example.demo.filter.EncodeHandleFilter;
import com.example.demo.filter.JwtTokenVerifyFilter;
import com.example.demo.handler.*;
import com.example.demo.service.UserDetailServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.time.Duration;
import java.util.Arrays;
/**
* @author : sheep669
* @description : spring security 的 配置类
* @created at 2022/7/11 16:50
*/
@Configuration
public class SecurityConfig {
/**
* 注入验证码验证过滤器
*
* @author sheep669
* @created at 2022/7/18 14:50
*/
@Autowired
private CaptchaVerifyFilter captchaVerifyFilter;
/**
* Token验证是否存在过滤器
*
* @author sheep669
* @created at 2022/7/18 14:56
*/
@Autowired
private JwtTokenVerifyFilter jwtTokenVerifyFilter;
/**
* 配置加密方式, 替换默认的 PasswordEncoder 采用 spring security 提供的 BCryptPasswordEncoder
*
* @return PasswordEncoder
* @author sheep669
* @created at 2022/7/17 18:34
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
// return NoOpPasswordEncoder.getInstance(); 让密码明文存储也可登录 过时了
}
/**
* 注入UserDetailsService返回自定义的实现类
*
* @return UserDetailsService
* @author sheep669
* @created at 2022/7/17 21:18
*/
@Bean
public UserDetailsService userDetailsService() {
return new UserDetailServiceImpl();
}
/**
* 登录成功处理器
*
* @author sheep669
* @created at 2022/7/17 18:55
*/
@Autowired
private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
/**
* 登录失败处理器
*
* @author sheep669
* @created at 2022/7/17 18:55
*/
@Autowired
private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
/**
* 用户未登录处理器
*
* @author sheep669
* @created at 2022/7/17 18:55
*/
@Autowired
private MyAuthenticationEntryPoint myAuthenticationEntryPoint;
/**
* 权限不足处理器
*
* @author sheep669
* @created at 2022/7/17 18:55
*/
@Autowired
private MyAccessDeniedHandler myAccessDeniedHandler;
/**
* 注销处理器
*
* @author sheep669
* @created at 2022/7/17 18:55
*/
@Autowired
private MyLogoutHandler myLogoutHandler;
/**
* 注销成功处理器
*
* @author sheep669
* @created at 2022/7/17 18:55
*/
@Autowired
private MyLogoutSuccessHandler myLogoutSuccessHandler;
@Autowired
private AccountVerifyFilter accountVerifyFilter;
@Autowired
private EncodeHandleFilter encodeHandleFilter;
/**
* 配置过滤拦截
* anonymous() 允许匿名用户访问,不允许已登入用户访问
* permitAll() 不管登入,不登入 都能访问
*
* @param http 请求配置
* @return SecurityFilterChain
* @author sheep669
* @created at 2022/7/17 18:35
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/captcha").anonymous()
.antMatchers("/get_menu_data").anonymous()
.anyRequest().authenticated()
.and().addFilterBefore(captchaVerifyFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtTokenVerifyFilter, CaptchaVerifyFilter.class)
.addFilterBefore(accountVerifyFilter, JwtTokenVerifyFilter.class)
.addFilterBefore(encodeHandleFilter, AccountVerifyFilter.class)
.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.successHandler(myAuthenticationSuccessHandler)
.failureHandler(myAuthenticationFailureHandler)
.and().exceptionHandling()
.authenticationEntryPoint(myAuthenticationEntryPoint)
.accessDeniedHandler(myAccessDeniedHandler)
.and().logout().permitAll()
.addLogoutHandler(myLogoutHandler)
.logoutSuccessHandler(myLogoutSuccessHandler)
.deleteCookies("JSESSIONID")
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().csrf().disable()
.cors().configurationSource(corsConfigurationSource());
return http.build();
}
/**
* 注入授权提供者, 设置授权实现类和密码校验
*
* @return DaoAuthenticationProvider
* @author sheep669
* @created at 2022/7/17 21:19
*/
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
/**
* 配置跨域
*
* @return CorsConfigurationSource
* @author sheep669
* @created at 2022/7/22 14:01
*/
@Bean
public CorsConfigurationSource corsConfigurationSource() {
// 新建一个跨域配置
CorsConfiguration configuration = new CorsConfiguration();
// 允许跨域访问的域名
configuration.setAllowedOrigins(Arrays.asList("http://localhost:8081", "http://localhost:8082"));
// 允许访问的方法名
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "DELETE", "PUT"));
// 允许请求携带验证信息
configuration.setAllowCredentials(true);
// 跨域允许时间 3秒
configuration.setMaxAge(Duration.ofMillis(3000));
// 允许服务端访问客户端的请求头
configuration.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
// 允许跨域访问的请求 所有请求 /**
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
核心实现
package com.example.demo.service;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.jwt.JWTUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.demo.mapper.UserMapper;
import com.example.demo.pojo.User;
import com.example.demo.utils.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* @author : sheep669
* @description : TODO
* @created at 2022/7/17 15:26
*/
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Autowired
private RedisUtil redisUtil;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUsername, username);
User user = userMapper.selectOne(queryWrapper);
if (ObjectUtil.isNull(user)) {
throw new UsernameNotFoundException("用户不存在!");
} else {
Map<String, Object> map = new HashMap<>(16);
map.put("userInfo", user.getUsername());
String token = JWTUtil.createToken(map, user.getUsername().getBytes());
redisUtil.setValueByKey("token", token);
redisUtil.setValueByKey("key", user.getUsername());
redisUtil.setValueByKey("role", user.getRole());
}
ArrayList<GrantedAuthority> authorities = new ArrayList<>();
SimpleGrantedAuthority role = new SimpleGrantedAuthority(user.getRole());
authorities.add(role);
return new org.springframework.security.core.userdetails.User(user.getUsername(),
user.getPassword(), authorities);
}
}