SpringSecurity6快速入门教程
SpringSecurity是Spring体系中的重要一部分,主要负责请求的鉴权与授权功能,本教程旨在快速入门SpringSecurity。
一、引入SpringSecurity依赖
通过maven引入SpringSecurity依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
二、实现UserDetails接口
UserDetails是SpringSecurity中用来存储并获取用户信息的接口,可自定义实现UserDetails来进行存储自定义用户信息及权限信息;也可同时实现UserDetailsService接口,通过UserDetailsService接口的loadUserByUsername方法获取自定义的UserDetails。UserDetails是用来对请求接口放行授权时重要的类,可在授权时传入用户信息,并传入用户权限。此案例是自定义一个简单的用户信息,实际项目中可通过向数据库中获取用户信息及权限,或者通过从Redis中获取缓存的用户信息。
public class MyUserDetails implements UserDetails {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<String> roles = new ArrayList<String>();
roles.add("admin");
roles.add("user");
List<SimpleGrantedAuthority> authorities = roles.stream()
.map(SimpleGrantedAuthority::new) // 将字符串包装为 SimpleGrantedAuthority
.toList();
return authorities;
}
@Override
public String getPassword() {
String password = "123456";
return password;
}
@Override
public String getUsername() {
String username = "admin";
return username;
}
}
三、手动创建Filter过滤器
手动创建一个Filter用来对请求进行鉴权操作;案例中首先对请求中的cookie进行判断,查看是否存有项目定义的cookie内容,若存在,则说明已进行授权,可对请求放行(在实际项目中,若存在cookie也需要对cookie中的内容进行判断,判断当前存储的cookie用户信息是否和登录的用户信息相符,若不相符,仍需要进行重新登录并生成新的cookie),若cookie不存在,则说明之前未进行登录过,此时判断拦截的接口是否为登录接口,若为登录接口,判断用户信息是否正确(实际项目中应从数据库中获取用户信息并进行对比是否正确),若用户信息正确,则生成一个包含用户信息的cookie并返回给浏览器并在SpringSecurity中设置授权认证并放行请求,若不是请求接口,则不做认证处理,会被SpringSecurity拦截到为未授权,无法访问信息,并跳转到登录页面。
public class MySecurityFilter extends OncePerRequestFilter {
private final String MY_COOKIE_NAME = "My_Cookie";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
MyUserDetailsService userDetailsService = new MyUserDetailsService();
// 从请求中获取Cookie
Cookie cookie = getCookie(request, MY_COOKIE_NAME);
// 如果Cookie存在,则说明浏览器登录过,对请求放行
if (cookie != null){
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetailsService.loadUserByUsername("admin"), null, userDetailsService.loadUserByUsername("admin").getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}else if ("/auth/login".equals( request.getServletPath())) { // 判断请求路径是否为登录路径,若为登录路径对登录用户进行判断
String queryString = request.getQueryString();
if (queryString != null){
String[] split = queryString.split("&");
Map<String, String> paramater = new HashMap<>();
for (String temp : split) {
String[] split1 = temp.split("=");
paramater.put(split1[0],split1[1]);
}
// 如果登录用户存在,则进行设置Cookie并放行
if (paramater.get("userName").equals("admin") && paramater.get("password").equals("123456")) {
Cookie myCookie = new Cookie(MY_COOKIE_NAME, paramater.get("userName"));
myCookie.setHttpOnly(true); //禁止JavaScript访问
myCookie.setPath("/"); //设置Cookie适用路径
myCookie.setMaxAge(60 *60); //设置有效期为1小时
myCookie.setSecure(false); // 若使用HTTPS,设置为true
response.addCookie(myCookie);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetailsService.loadUserByUsername("admin"), null, userDetailsService.loadUserByUsername("admin").getAuthorities());
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
}else {
System.out.println("鉴权失败了!!!!");
}
filterChain.doFilter(request,response);
}
private Cookie getCookie(HttpServletRequest request, String name) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(name)) {
return cookie;
}
}
}
return null;
}
}
四、配置SpringSecurity配置类
自定义SpringSecurity配置类。配置类中有以下几点需要注意:
一)、老版本配置SpringSecurity直接继承了WebSecurityConfigurerAdapter 这个类,但是在SpringSecurity6版本之后将这个类移除了。
二)、还有一点就是在配置类中引入自定义Filter,采用将方法声明为Bean的方式,这样可以防止将自定义Filter整体设置为组件,从而被SpringSecurity和SpringBoot均配置为Bean引起请求时执行两次。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.csrf(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable) //禁用默认登录页面
.authorizeHttpRequests(auth -> auth
.requestMatchers("/favicon.ico").permitAll()
.anyRequest().authenticated())
.addFilterBefore(mySecurityFilter() , UsernamePasswordAuthenticationFilter.class)
.exceptionHandling(exception -> exception
.accessDeniedHandler((request, response, accessDeniedException) -> {
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{"error": "无权限访问"}");
}).authenticationEntryPoint((request, response, authException) -> {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{"error": "未认证,请先登录"}");
// response.sendRedirect("/login"); //此处可重定向到登录页面
})
)
.build();
}
@Bean
public MySecurityFilter mySecurityFilter() {
return new MySecurityFilter();
}
}
五、总结
SpringSecurity整体入门还是比较简单的,只要理清是在哪个方面进行安全防护即。SpringSecurity的原理就是自定义Filter过滤器,将每次请求进行判断是否符合预期,若不符合预期则进行拦截,若符合预期则进行放行。以下是我对SpringSecurity的理解流程图。
项目源码地址:github.com/AngelSongKu… gitee.com/Mr_Song_Kun…
觉得有用的话就点个Start吧,万分感谢!