文章目录
- 开头
- 简介
- 环境搭建
- 入门使用
- 1、认证
- 1、实体类
- 2、Controller层
- 3、Service层
- 3.1、接口
- 3.2、实现类
- 3.3、实现类:UserDetailsServiceImpl
- 4、Mapper层
- 3、自定义token认证filter
- 注意事项
- 小结
开头
- Spring Security 官方网址:Spring Security官网
开头贴官网,有事找官方!
简介
介绍的话不多说,就一句:
-
Spring Security 是一个安全管理框架。
- 一般用于中大型项目。小项目使用shiro,shiro上手简单。
- 不过,一般是这样。小项目练手用也是相当可以的。
好吧!这是三句话,没跑。
环境搭建
基于SpringBoot3搭建的项目,妥妥好用。
组件 | SpringBoot2.X | SpringBoot3.X |
---|---|---|
JDK | JDK 8、9 | JDK 17+ |
JPA | JPA2.0+ | JPA3.0+ |
Servlet | Servlet 3.1+ | Servlet 5.0 |
Spring | Spring Framework 5+ | Spring Framework 6+ |
Gradle | Gradle 4.x | Gradle7.3 |
1、依赖导入
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
</parent>
<!-- spring web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring Security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.7</version>
</dependency>
<!-- mysql驱动-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
2、配置文件
spring:
datasource:
url: jdbc:mysql://localhost:3306/dataBase?characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
# mybatisPlus 配置
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
# 日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 逻辑删除
logic-delete-field: delFag
logic-delete-value: 1
logic-not-delete-value: 0
# 主键自增
id-type: auto
入门使用
1、认证
废话不多说,贴个认证流程图:
1、SpringSecurity Config类
//整点实在的,一整套流程,绝对全面。
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private AuthenticationTokenFilter tokenFilter;
@Autowired
private AuthenticationEntryPointImpl authenticationEntryPoint;
/*
PasswordEncoder是一个用于密码加密的接口,它封装了多种主流的加密方法,
它们用于密码的安全存储和校验。但计算开销也相对较大,
因此在面对高并发性能要求的大型信息系统时:
推荐使用会话、OAuth、Token等短期加密策略来实现系统的信息安全。
*/
@Bean
public PasswordEncoder getPasswordEncoder(){
return new BCryptPasswordEncoder();
}
/*
负责注册为应用程序提供认证服务的 。
*/
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config ) throws Exception {
return config.getAuthenticationManager();
}
/*
Security Filter是通过FilterChainProxy而不是DelegatingFilterProxy注册进SecurityFilterChain的。
过滤器链,配置
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
http.csrf(csrf -> csrf.disable()); //关闭csrf防护
http.authorizeHttpRequests(auth ->
auth
.requestMatchers("/user/login")//请求路径匹配
.permitAll()//放行,不作拦截
.anyRequest()//其他请求
.authenticated());//认证
// 自定义token 认证,用于登录后续请求放行。通过springSecurity上下文判断
http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
1、实体类
//主要就是用:username和 password
@Data
public class SysUser implements Serializable {
private static final long serialVersionUID = 662137028719131857L;
private Long id;
/**
* 用户名
*/
private String username;
/**
* 昵称
*/
private String nickname;
/**
* 密码
*/
private String password;
/**
* 用户类型:0:普通 1:管理员
*/
private String type;
/**
* 账号状态:0:正常 1:停用
*/
private String status;
/**
* 邮箱
*/
private String email;
/**
* 手机号
*/
private String phoneNumber;
/**
* 用户性别(0:男 1:女 2 :匿名)
*/
private String sex;
/**
* 头像
*/
private String avatar;
private Integer isDelete;
}
2、Controller层
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private SysUserService userService;
@PostMapping("/login")
public R login(@RequestBody SysUser user){
return userService.login(user);
}
}
3、Service层
3.1、接口
public interface SysUserService extends IService<SysUser> {
/**
* 登录
* @param user
* @return
*/
R login(SysUser user);
}
3.2、实现类
@Service
public class SysUserServiceImpl extends ServiceImpl<UserMapper, SysUser> implements SysUserService {
@Autowired
private AuthenticationManager authenticationManager;
@Override
public R login(SysUser user) {
//重写userDetailsService 处理
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
if(Objects.isNull(authenticate)){
throw new AppException(AppExceptionMsgEnum.LOGIN_ERROR);
}
//jwt令牌信息
//……
LoginUser loginuser = (LoginUser) authenticate.getPrincipal();
//自定义返回
return R.success(loginuser.getUser());
}
}
3.3、实现类:UserDetailsServiceImpl
- 用于用户信息认证处理
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getUsername,username);
SysUser userResult = userMapper.selectOne(queryWrapper);
if(Objects.isNull(userResult)){
throw new AppException(AppExceptionMsgEnum.LOGIN_ERROR);
}
return new LoginUser(userResult);
}
}
4、Mapper层
public interface UserMapper extends BaseMapper<SysUser> {
}
3、自定义token认证filter
@Component
public class AuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private UserMapper userMapper;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, ServletException, IOException {
//获取token
String token = request.getHeader("token");
String uri = request.getRequestURI();
if(uri.equals("/gate/logout")){
filterChain.doFilter(request,response);
return;
}
//放行未携带token 的请求,交由后面拦截器处理
if (token == null) {
filterChain.doFilter(request,response);
return;
}
//获取用户信息,这里由于没有使用jwt,所以仿制了一下
SysUser user = new SysUser();
user.setUsername(token);
LoginUser loginUser = new LoginUser();
loginUser.setUser(user);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null,null);
//存储到SecurityContextHolder
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
filterChain.doFilter(request,response);
}
}
注意事项
- 认证失败后,会抛出异常。使用全局异常处理器 + 自定义异常 使用。
- 自定义返回类
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)// 属性为NULL 不序列化
public class R<T> {
private int status;
// private boolean success;
private String message;
private T data;
@JSONField(format="yyyy-MM-dd HH:mm:ss")
private DateTime time;
/**
* 正确result
* @param data
* @param <T>
* @return
*/
public static <T> R<T> success(T data){
R<T> r = new R<>();
// r.success = true;
r.status = 200;
r.data = data;
r.message = "successful";
r.time = DateTime.now();
return r;
}
public static <T> R<T> success(String msg ,T data){
R<T> r = new R<>();
// r.success = true;
r.status = 200;
r.data = data;
r.message = msg;
r.time = DateTime.now();
return r;
}
/**
* 错误result
* @param msg
* @param status
* @param <T>
* @return
*/
public static <T> R<T> error(String msg,int status){
R<T> r = new R<>();
// r.success = false;
r.status = status;
r.message = msg;
r.time = DateTime.now();
return r;
}
/**
* 错误result
* @param msg
* @param <T>
* @return
*/
public static <T> R<T> error(String msg){
R<T> r = new R<>();
// r.success = false;
r.message = msg;
r.time = DateTime.now();
return r;
}
public static <T> R<T> error(AppExceptionMsgEnum appExceptionMsg){
R<T> r = new R<>();
// r.success = false;
r.status = appExceptionMsg.getStatus();
r.message = appExceptionMsg.getMsg();
r.time = DateTime.now();
return r;
}
}
- 全局异常处理器
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(value = BadCredentialsException.class)
public static <T> R<T> badCredentialsException(BadCredentialsException e) throws InterruptedException {
return R.error(AppExceptionMsgEnum.LOGIN_ERROR);
}
@ExceptionHandler(value = Exception.class)
public static <T> R<T> exceptionHandle(Exception e) throws InterruptedException {
if(e instanceof AppException){
AppException app = (AppException) e;
return R.error(app.getMsg(),app.getStatus());
}
return R.error(e.toString(),500);
}
}
- 自定义异常
@EqualsAndHashCode(callSuper = true)
@Data
public class AppException extends RuntimeException{
private int status;
private String msg;
public AppException(AppExceptionMsgEnum appExceptionMsgEnum) {
this.status = appExceptionMsgEnum.getStatus();
this.msg = appExceptionMsgEnum.getMsg();
}
}
- 异常枚举类
public enum AppExceptionMsgEnum {
//成功
SUCCESS(200,"操作成功"),
//失败
NEED_LOGIN(401,"需要登录后操作"),
NO_OPERATION_AUTH(403,"无权限操作"),
USERNAME_EXIST(501,"用户名已存在"),
PHONE_NUMBER_EXITS(502,"手机号已存在"),
EMAIL_EXIST(503,"邮箱已存在"),
REQUIRED_USERNAME(504,"必须填写用户名"),
LOGIN_ERROR(505," 用户名或密码错误"),
WRONG_PAGE_PARAM(506,"wrong page param"),
CONTENT_NOT_NULL(507,"评论内容不能为空"),
FILENAME_ERROR(508,"上传文件错误"),
ONLY_UPDATE_SELF(509,"只能修改自己的信息"),
SERVER_ERROR(500,"SERVER ERROR")
;
private int status;
private String msg;
AppExceptionMsgEnum(int status, String msg) {
this.status = status;
this.msg = msg;
}
public int getStatus() {
return status;
}
public String getMsg() {
return msg;
}
}
例如:
- API
- 支持模型类型
小结
-
SpringSecurity5.7.0之前
- 常见的 Spring HTTP Security 配置类都会继承一个 WebSecurityConfigureAdapter 类。
-
从 5.7.0-M2 起,WebSecurityConfigureAdapter 被废弃了,不推荐使用。
- 组件化开始,更加灵活。
-
基础认证功能完成。完结撒花!!