一:初始化项目
1、创建SpringBoot项目
<!--spring boot-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2、整合MyBatis(具体参考我的《Spring Boot整合MyBatis》)
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
3、引入SpringSecurity
<!--spring security 启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
4、创建测试接口http://localhost:8080/hello,进行测试。
当引入security成功之后,在登录测试接口时就会跳转到默认的登录页http://localhost:8080/login。
(默认用户名:user,密码:控制台输出,我这里是28f2036e-c4f2-4097-9a5c-17e7c8a429c4,)
二:简单原理介绍
1、登录校验流程
2、SpringSecurity简单过滤器链(完整的过滤器大概是14个),前后端分离一般用jwt,前后端不分离一般采用session
三:认证(UsernamePasswordAuthenticationFiter)
1、登录流程:
①、登录、自定义登录接口:
调用ProviderManager的方法进行认证,如果认证通过生成jwt和把用户信息存到redis中。
// 认证 实现代码类
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(sysUser.getUserName(), sysUser.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
// 认证通过 生成jwt
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userId = loginUser.getSysUser().getId().toString();
String jwt = JwtUtil.createJWT(userId);// 使用userId生成 jwt
Map<String, String> map = new HashMap<>(1);
map.put("token", jwt);
// 认证通过 存入 redis
redisCache.setCacheObject("login:" + userId, loginUser);
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//认证 配置代码
@Override
protected void configure(HttpSecurity http) throws Exception {
// ......
}
}
②、登录、自定义UserDetailsService:
实现到db中查询用户信息,因为原接口是从内存中查询的。
//重写 UserDetailsService 的 loadUserByUsername 方法
public class UserDetailsServiceImpl implements UserDetailsService{
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//......
}
}
//重写UserDetails返回的用户信息
public class LoginUser implements UserDetails {
//......
}
2、token认证:
①、校验、定义jwt认证过滤器:
获取toekn、解析token获取其中的userId、从redis中获取用户信息、使用SecurityContextHolder.getContext().setAuthentication()方法存储该对象,这样其他过滤器会通过SecurityContextHolder来获取当前用户信息。
// token认证过滤器
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 省略...... 拦截到 token不合法等情况
// 将 Authentication对象存入 SecurityContextHolder
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterChain.doFilter(request, response);
}
}
四:授权(FilterSecurityInterceptor)
1、将用户的权限信息封装到Authentcation当中,存到SecurityContextHolder中。
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 省略 ......
// 根据用户id查询权限字符串集合
List<String> list = sysMenuDao.selectPermsByUserId(userInfo.getId());
return new LoginUser(userInfo, list);
}
}
public class LoginUser implements UserDetails {
// ......
@JSONField(serialize = false) //fastjson注解,表示此属性不会被序列化到redis当中
private List<SimpleGrantedAuthority> authorities;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 权限为空的时候才往遍历,不为空直接返回
if (authorities != null) {
return authorities;
}
//把permissions中String类型的权限信息封装成SimpleGrantedAuthority对象
authorities = new ArrayList<>();
for (String permission : permissions) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission);
authorities.add(authority);
}
return authorities;
}
// ......
}
// token过滤器中( JwtAuthenticationTokenFilter )
// 将Authentication对象(用户信息、已认证状态、权限信息)存入 SecurityContextHolder
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
2、从SecurityContextHolder中获取Authentcation对象,再获取其中的权限信息。
// token过滤器中( JwtAuthenticationTokenFilter )
String redisKey = "login:" + userId;
LoginUser loginUser = redisCache.getCacheObject(redisKey);// 从redis中获取用户信息
3、设置每个资源(方法)所需要的权限(注解形式设置权限)
RBAC(基于角色的权限控制)
//SecurityConfig 配置类开启权限注解功能
@EnableGlobalMethodSecurity(prePostEnabled = true)
/***** 对应资源(方法)上授权 *****/
//常用,该用户在数据库中有 system:dept:list 这个权限标识才能访问
@PreAuthorize("hasAuthority('system:dept:list')")
@PreAuthorize("hasAnyAuthority()")
@PreAuthorize("hasAnyRole()")
@PreAuthorize("hasAnyRole()")
@PreAuthorize("hasPermission()")
五:失败处理(ExceptionTranslationFilter)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// ......
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling() // 配置异常处理器
.authenticationEntryPoint(authenticationEntryPoint)// 认证失败
.accessDeniedHandler(accessDeniedHandler); // 授权失败
}
// ......
}
1、认证失败处理器
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
// ......
}
2、授权失败处理器
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
// ......
}
六:跨域
同时处理 springboot跨域 和 springsecurity跨域
// springboot 跨域配置类
@Configuration
public class