文章目录
- 原理
- 使用
- 自定义权限校验
- 主要类
- 通过debug的方式查看security有哪些过滤器
- 配置类
- UsernamePasswordAuthenticationFilter
- UserDetailsService
- ExceptionTranslationFilter
- 自定义认证和授权异常处理
- FilterSecurityInterceptor权限校验
- 创建拦截器获取用户权限并传递给security
- 创建controller使用注解添加权限认证
- WebSecurityConfigurerAdapter
- BCryptPasswordEncoder
- UserDetails
- AuthenticationEntryPoint
- JWT实现认证授权
原理
SpringSecurity的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器,
使用
需要的依赖
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.4</version>
</parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency></dependencies>
自定义权限校验
在需要校验的方法上添加此注解
@PreAuthorize(“@ex.hasAuthority(‘ROLE_ADMIN’)”)
ex为自定义的类的别名,
hasAuthority为自定义校验权限的方法
自定义的类方法
@Service("ex")
public class CustomAuthority {public boolean hasAuthority(String authority) {// 获取当前用户的所有权限// 判断用户权限集合中是否包含authorityreturn true; // 模拟有权限}
}
主要类
通过debug的方式查看security有哪些过滤器
配置类
package org.example.config;import org.example.filter.JWTAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate AuthenticationEntryPoint authenticationEntryPoint;@Autowiredprivate AccessDeniedHandler accessDeniedHandler;@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {String[] urls = {"/user/login",}; // 需要放行的地址http.csrf().disable() // 关闭csrf.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() // 每次调用and都会返回一个新的HttpSecurity并可以再次调用HttpSecurity对象的方法.authorizeRequests().antMatchers(urls).anonymous() // 可以匿名访问的地址.antMatchers("/user/login").permitAll() // 允许所有用户都有权限访问.anyRequest().authenticated(); // 除可以匿名访问的地址外,其他地址都要认证// 添加过滤器,第一个参数是要添加的过滤器,第二个参数是要添加在哪个过滤器之前,第二个参数必须是security已经管理的过滤器http.addFilterBefore(new JWTAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);// 设置认证和授权异常处理http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler);}@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}
}
UsernamePasswordAuthenticationFilter
负责我们在登录页面填写了用户密码后的登录请求
UserDetailsService
可以通过此接口的loadUserByUsername方法实现自定义的用户认证
package org.example.service;import org.example.domain.LoginUser;
import org.example.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;@Service
public class UserDetailServiceImpl implements UserDetailsService {@Autowiredprivate PasswordEncoder passwordEncoder;@Overridepublic UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {// 查询用户信息并,此处模拟查询的用户信息User user = new User();user.setUserName("test");BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
// user.setPassword("{noop}123"); // {noop}是为了不用给密码加密,后面跟的是密码user.setPassword(passwordEncoder.encode("123"));// 查询权限信息,此处模拟return new LoginUser(user);}
}
ExceptionTranslationFilter
处理过滤器链中抛出任何AccessDeniedException和AuthenticationException
自定义认证和授权异常处理
我们希望在认证失败或者授权失败的情况下和业务代码一样返回相同的数据结构,这样可以让前端对响应进行统一的处理。
在springsecurity中,如果我们在认证或者授权的过程中出现了异常的会被ExceptionTranslationFilter捕获到,在ExceptionTranslationFilter中会判断是认证失败还是授权失败出现的异常。
如果认证过程中出翔异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法进行异常处理。
如果是授权过程中出现了异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法进行异常处理。
所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPoint和AccessDeniedHandler然后配置被springsecurity即可
。
AuthenticationEntryPoint
package org.example.handler;import org.example.util.WebUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Service;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@Service
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {e.printStackTrace();WebUtil.renderString(httpServletResponse, "认证异常");}
}
AccessDeniedHandler
package org.example.handler;import org.example.util.WebUtil;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Service;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@Service
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {e.printStackTrace();WebUtil.renderString(httpServletResponse, "没有权限");}
}
WebUtil
package org.example.util;import javax.servlet.http.HttpServletResponse;public class WebUtil {public static void renderString(HttpServletResponse response, String responseStr) {response.setStatus(200);response.setContentType("application/json");response.setCharacterEncoding("utf-8");try {response.getWriter().print(responseStr);} catch (Exception e) {e.printStackTrace();}}
}
FilterSecurityInterceptor权限校验
需要现在配置类上先开启权限校验配置
使用@EnableGlobalMethodSecurity(prePostEnabled = true)
再在需要权限校验的方法上使用注解
负责权限校验的过滤器,认证时会通过SecurityContextHolder.getContext().setAuthentication(authenticationToken);保存用户不权限集合
创建拦截器获取用户权限并传递给security
package org.example.filter;import org.example.domain.LoginUser;
import org.example.domain.User;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {// 伪代码// 获取token// 解析token// 获取用户信息,此处模拟获取用户信息LoginUser loginUser = new LoginUser();User user = new User();user.setUserName("test");BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
// user.setPassword("{noop}123"); // {noop}是为了不用给密码加密,后面跟的是密码user.setPassword(passwordEncoder.encode("123"));loginUser.setUser(user);loginUser.setPermissions(Arrays.asList("admin", "test"));// 此处用三个参数的构造函数,因为用了此函数后不会再走用户登录的认证逻辑// 三个参数的构造函数,第一个参数是登录用户,第二个参数是密码,// 第三个参数是权限集合,当权限校验时security会使用默认的过滤器FilterSecurityInterceptor校验此集合数据UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
// SecurityContextHolder// 存入SecurityContextHolder中,用于后续的过滤器使用用户信息SecurityContextHolder.getContext().setAuthentication(authenticationToken);// 放行filterChain.doFilter(httpServletRequest, httpServletResponse);}
}
创建controller使用注解添加权限认证
package org.example.controller;import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class HelloController {@GetMapping("/hello")@PreAuthorize("hasAuthority('admin')") // 此是调用的security的hasAuthority方法public String hello() {return "Hello World!";}
}
WebSecurityConfigurerAdapter
配置抽象类,可以通过实现此抽象类,实现里面的protected void configure(HttpSecurity http)方法实现security的自定义配置
BCryptPasswordEncoder
对密码进行加密和匹配
- encode:对密码进行加密方法
- matches:对明文密码和加密过的密码进行密码验证
UserDetails
编写用户类实现此接口,用于保存用户信息和鉴权
- getAuthorities:用于将用户的权限传递给SpringSecurity,将权限字符串封装成SimpleGrantedAuthority并传递给SpringSecurity
package org.example.domain;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
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
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails {/*** 项目中的用户类,用与从数据库中查询保存结果*/private User user;private List<String> permissions; // 权限校验@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {List<SimpleGrantedAuthority> collect = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());return collect;}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUserName();}/*** 判断用户是否没过期* @return*/@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}
AuthenticationEntryPoint
异常处理的接口,实现此接口的方法,将实现类设置到security中,有认证异常会调用实现类的方法