DefaultControllerAdvice.java
package com.stu.manage.demo.config;
import com.stu.manage.demo.result.Result;
import com.stu.manage.demo.result.ResultEnum;
import com.stu.manage.demo.result.ResultUtil;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 捕获Control层抛出的异常 做统一通知处理
*/
/*@ControllerAdvice
@ResponseBody
public class DefaultControllerAdvice {
@ExceptionHandler(Exception.class)
public Result exceptionHandler() {
return ResultUtil.error(ResultEnum.UNKNOWN_ERROR.getCode(),ResultEnum.UNKNOWN_ERROR.getMsg());
}
}*/
MybatisPlusConfig.java
package com.stu.manage.demo.config;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
// 配置类
// 如果这里使用 @MapperScan 则 在启动类 SpingbootTestApplication@MapperScan可以不用写
//@MapperScan("com.lomonkey.mapper")
@Configuration
@EnableTransactionManagement // 开启注解事务管理,等同于xml配置文件中的
public class MybatisPlusConfig implements TransactionManagementConfigurer {
@Resource(name="txManager1")
private PlatformTransactionManager txManager1;
/**
* 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false
* 避免缓存出现问题(该属性会在旧插件移除后一同移除)
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInnerInterceptor.setOverflow(true);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInnerInterceptor.setMaxLimit(50L);
// 开启 count 的 join 优化,只针对部分 left join
//paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
// paginationInnerInterceptor.setDialect();
// 添加分页拦截器插件
interceptor.addInnerInterceptor(paginationInnerInterceptor);
// 添加乐观锁配置
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> configuration.setUseDeprecatedExecutor(false);
}
/*// 注册乐观锁插件
@Bean
public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
return new OptimisticLockerInnerInterceptor();
}*/
// 创建事务管理器1 其中DataSource会自动注入
//在Spring容器中,我们手工注解@Bean 将被优先加载,框架不会重新实例化其他的 PlatformTransactionManager 实现类。
// 关于事务管理器,不管是JPA还是JDBC等都实现自接口 PlatformTransactionManager
// 如果你添加的是 spring-boot-starter-jdbc 依赖,框架会默认注入 DataSourceTransactionManager 实例。
// 如果你添加的是 spring-boot-starter-data-jpa 依赖,框架会默认注入 JpaTransactionManager 实例。
@Bean(name = "txManager1")
public PlatformTransactionManager txManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
// 创建事务管理器2
/*@Bean(name = "txManager2")
public PlatformTransactionManager txManager2(EntityManagerFactory factory) {
return new JpaTransactionManager(factory);
}*/
// 实现接口 TransactionManagementConfigurer 方法,其返回值代表在拥有多个事务管理器的情况下默认使用的事务管理器
@Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return txManager1;
}
}
MyMetaObjectHandler
package com.stu.manage.demo.config;
import java.util.Date;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component //一定不要忘记吧处理器加到IOC容器中
public class MyMetaObjectHandler implements MetaObjectHandler {
//插入时候的填充策略
@Override
public void insertFill(MetaObject metaObject) {
//设置字段的值(String fieldName字段名,Object fieldVal要传递的值,MetaObject metaObject)
this.setFieldValByName("createTime",new Date(),metaObject);
this.strictInsertFill(metaObject, "modifyTime", Date.class, new Date()); // 起始版本 3.3.0(推荐使用)
}
//更新时间的填充策略
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "modifyTime", Date.class, new Date()); // 起始版本 3.3.0(推荐使用)
}
}
SecurityConfiguration.java
package com.stu.manage.demo.config;
import java.util.Arrays;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import com.stu.manage.demo.config.service.UserDetailsServiceImpl;
import com.stu.manage.demo.constants.SecurityConstants;
import com.stu.manage.demo.exception.JwtAccessDeniedHandler;
import com.stu.manage.demo.exception.JwtAuthenticationEntryPoint;
import com.stu.manage.demo.filter.JWTAuthenticationFilter;
import com.stu.manage.demo.filter.JwtAuthorizationFilter;
import static java.util.Collections.singletonList;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final StringRedisTemplate stringRedisTemplate;
@Autowired
private JwtAccessDeniedHandler jwtAccessDeniedHandler;
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
public SecurityConfiguration(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
/**
* 密码编码器
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
protected UserDetailsService userDetailsService() {
return new UserDetailsServiceImpl();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 设置自定义的userDetailsService以及密码编码器
auth.userDetailsService(userDetailsService()).passwordEncoder(bCryptPasswordEncoder());
}
/*@Bean
public JwtAuthorizationFilter jwtAuthorizationFilter() throws Exception {
JwtAuthorizationFilter filter = new JwtAuthorizationFilter(authenticationManager(), stringRedisTemplate);
return filter;
}*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors(withDefaults())
.csrf().disable() // 禁用 CSRF 禁用自带的跨域策略
.authorizeRequests()
// 指定的接口直接放行
.antMatchers(SecurityConstants.SWAGGER_WHITELIST).permitAll() // swagger
//.antMatchers(HttpMethod.GET, SecurityConstants.SYSTEM).permitAll()
.antMatchers(HttpMethod.POST, SecurityConstants.AUTH_LOGIN_URL).permitAll()
//.antMatchers(SecurityConstants.HAS_AUTH).hasAuthority("SystemUserUpdate")
//.antMatchers("student/insert/*").hasAuthority("SystemUserInsert")
.anyRequest().authenticated() // 其他的接口都需要认证后才能请求
.and()
//添加自定义Filter
.addFilter(new JWTAuthenticationFilter(authenticationManager(), stringRedisTemplate))
.addFilter(new JwtAuthorizationFilter(authenticationManager(), stringRedisTemplate))
// 不需要session(不创建会话)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// 授权异常处理
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler);
// 防止H2 web 页面的Frame 被拦截
http.headers().frameOptions().disable();
/*http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement()
.and()
.logout()
.and()
.httpBasic();*/
}
/**
* Cors配置优化
**/
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(singletonList("*"));
// configuration.setAllowedOriginPatterns(singletonList("*"));
configuration.setAllowedHeaders(singletonList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "DELETE", "PUT", "OPTIONS"));
configuration.setExposedHeaders(singletonList(SecurityConstants.TOKEN_HEADER));
configuration.setAllowCredentials(false);
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
SwaggerConfig
package com.stu.manage.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.stu.manage.demo.controller")).build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("学生管理系统API")
.description("接口地址:https://")
.version("1.0").build();
}
}
SecurityConstants
package com.stu.manage.demo.constants;
public final class SecurityConstants {
/**
* 角色的key
**/
public static final String ROLE_CLAIMS = "rol";
/**
* rememberMe 为 false 的时候过期时间是1个小时
*/
public static final long EXPIRATION = 60 * 60L;
/**
* rememberMe 为 true 的时候过期时间是7天
*/
public static final long EXPIRATION_REMEMBER = 60 * 60 * 24 * 7L;
/**
* JWT签名密钥硬编码到应用程序代码中,应该存放在环境变量或.properties文件中。
*/
public static final String JWT_SECRET_KEY = "C*F-JaNdRgUkXn2r5u8x/A?D(G+KbPeShVmYq3s6v9y$B&E)H@McQfTjWnZr4u7w";
// JWT token defaults
public static final String TOKEN_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
public static final String TOKEN_TYPE = "JWT";
// Swagger WHITELIST
public static final String[] SWAGGER_WHITELIST = {
"/swagger-ui.html",
"/swagger-ui/*",
"/swagger-resources/**",
"/v2/api-docs",
"/v3/api-docs",
"/webjars/**",
//knife4j
"/doc.html",
};
public static final String[] SYSTEM = {
"/login"
};
// System WHITELIST
public static final String AUTH_LOGIN_URL = "/auth/login";
public static final String[] HAS_AUTH = {
"/student/update/*"
};
private SecurityConstants() {
}
}
LoginController
package com.stu.manage.demo.controller;
import com.stu.manage.demo.dto.LoginRequest;
import com.stu.manage.demo.entity.LoginToken;
import com.stu.manage.demo.result.Result;
import com.stu.manage.demo.result.ResultEnum;
import com.stu.manage.demo.result.ResultUtil;
import com.stu.manage.demo.service.IUserService;
import com.stu.manage.demo.service.LoginService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/auth")
@Slf4j
public class LoginController {
@Autowired
private LoginService loginService;
/**
* 登录验证:
* 方式一:直接调用该接口,在loginService.createToken(loginRequest)中进行了验证并生成token
* 方式二:自定义认证拦截器JWTAuthenticationFilter
*/
@PostMapping("/login-up")
public Result login(@RequestBody LoginRequest loginRequest) {
String token = null;
try {
token = loginService.createToken(loginRequest);
} catch (Exception e) {
log.error(e.getMessage());
return ResultUtil.error(ResultEnum.VERIFY_FAIL.getCode(), ResultEnum.VERIFY_FAIL.getMsg());
}
LoginToken loginToken = new LoginToken();
loginToken.setName(loginRequest.getUsername());
loginToken.setToken(token);
/*HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set(SecurityConstants.TOKEN_HEADER, token);*/
return ResultUtil.success(loginToken);
// return new ResponseEntity<>(httpHeaders, HttpStatus.OK);
}
}
StudentController
package com.stu.manage.demo.controller;
import com.stu.manage.demo.result.Result;
import com.stu.manage.demo.result.ResultEnum;
import com.stu.manage.demo.result.ResultUtil;
import com.stu.manage.demo.service.StudentService;
import com.stu.manage.demo.entity.Student;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
@RestController
@RequestMapping("/student")
@Slf4j
public class StudentController {
@Autowired
private StudentService studentService;
@GetMapping("/getAll")
// @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
@PreAuthorize("hasAuthority('SystemContent')")
public Result getAllStudents(){
List res = studentService.getAllStudents();
if(res!=null) {
return ResultUtil.success(res);
}else {
return ResultUtil.error(ResultEnum.DATA_IS_NULL.getCode(),ResultEnum.DATA_IS_NULL.getMsg());
}
}
@GetMapping("/find/id/{id}")
public Result studentById(@PathVariable("id")Integer id){
List res=studentService.getStudentById(id);
if(!res.isEmpty()){
return ResultUtil.success(res);
}else {
return ResultUtil.error(ResultEnum.STUDENT_NOT_EXIST.getCode(),ResultEnum.STUDENT_NOT_EXIST.getMsg());
}
}
@PostMapping ("/insert")
public Result insert(@RequestBody @Valid Student student){
int res=studentService.insertStudent(student);
if(res==1){
return ResultUtil.success(res);
}else {
return ResultUtil.error(ResultEnum.UNKNOWN_ERROR.getCode(),ResultEnum.UNKNOWN_ERROR.getMsg());
}
}
@PostMapping("/update")
//@PreAuthorize("hasAnyRole('ROLE_ROOT')")
@PreAuthorize("hasAuthority('SystemUserUpdate')")
public Result update(@RequestBody @Valid Student student){
int res=studentService.updateStudent(student);
if(res>0){
return ResultUtil.success();
}else {
return ResultUtil.error(ResultEnum.UPDATE_FAIL.getCode(),ResultEnum.UPDATE_FAIL.getMsg());
}
}
}
LoginRequest
package com.stu.manage.demo.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author cdc
* @description 用户登录请求DTO
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginRequest {
private String username;
private String password;
private Boolean rememberMe;
}
JwtUser
package com.stu.manage.demo.entity;
import java.util.Collection;
import java.util.List;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
public class JwtUser implements UserDetails {
private Long id;
private String username;
private String password;
private Boolean enabled;
private Collection extends GrantedAuthority> authorities;
public JwtUser(TbUser tbUser) {
this.id = tbUser.getId();
this.username = tbUser.getUserName();
this.password = tbUser.getPassWord();
this.enabled = tbUser.getEnabled() == null ? true : tbUser.getEnabled();;
}
public void setAuthorities(List list) {
this.authorities = list;
}
public JwtUser() {}
@Override
public Collection extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public Long getId() {
return id;
}
}
LoginToken
package com.stu.manage.demo.entity;
import lombok.Data;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@Data
@ToString
public class LoginToken {
@NotNull
private String name;
private String token;
}
Student
package com.stu.manage.demo.entity;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Builder;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
@Builder
public class Student {
/**
* 配置主键生成策略
*/
@TableId(value = "student_id", type = IdType.AUTO)
private Integer studentId;
@TableField("student_name")
@NotBlank(message = "姓名不能为空")
private String studentName;
@TableField("id_card")
@NotBlank(message = "身份证不能为空")
private String idCard;
@NotNull
private String sex;
@TableField("grade_id")
@NotNull
private Integer gradeId;
@TableField("class_id")
@NotNull
private Integer classId;
@TableField(value = "createtime", fill = FieldFill.INSERT)
private Date createTime;
@TableField(value = "modifytime", fill = FieldFill.INSERT_UPDATE)
private Date modifyTime;
/**
* 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
* 整数类型下 newVersion = oldVersion + 1
* newVersion 会回写到 entity 中
* 仅支持 updateById(id) 与 update(entity, wrapper) 方法
* 在 update(entity, wrapper) 方法下, wrapper 不能复用!!!
*/
@Version //乐观锁Version注解
private Integer version;
}
TbAuditBase
package com.stu.manage.demo.entity;
import java.util.Date;
import javax.validation.constraints.NotNull;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
public class TbAuditBase {
@TableField(value = "created", fill = FieldFill.INSERT)
@NotNull
private Date createTime;
@TableField(value = "updated", fill = FieldFill.UPDATE)
@NotNull
private Date modifyTime;
}
TbPermission
package com.stu.manage.demo.entity;
import javax.validation.constraints.NotNull;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class TbPermission extends TbAuditBase {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("parent_id")
@NotNull
private Long parentId;
@NotNull
private String name;
@NotNull
private String enname;
@NotNull
private String url;
private String description;
}
TbRole
package com.stu.manage.demo.entity;
import java.util.List;
import javax.validation.constraints.NotNull;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@TableName(value = "tb_role", resultMap = "BaseResultMap")
@AllArgsConstructor
@NoArgsConstructor
public class TbRole extends TbAuditBase {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("parent_id")
@NotNull
private Long parentId;
@NotNull
private String name;
@NotNull
private String enname;
private String description;
// 一对多
private List rolePermissions;
}
TbRolePermission
package com.stu.manage.demo.entity;
import javax.validation.constraints.NotNull;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class TbRolePermission {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("permission_id")
@NotNull
private Long permissionId;
@TableField("role_id")
@NotNull
private Long roleId;
}
TbUser
package com.stu.manage.demo.entity;
import java.util.List;
import javax.validation.constraints.NotNull;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@TableName(value = "tb_user", resultMap = "BaseResultMap")
@AllArgsConstructor
@NoArgsConstructor
public class TbUser extends TbAuditBase {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("username")
@NotNull
private String userName;
@TableField("password")
@NotNull
private String passWord;
@NotNull
private String phone;
private String email;
private Boolean enabled;
/**
* 用户表和用户权限中间表是一对多的关系
*/
private List userRoles;
}
TbUserRole
package com.stu.manage.demo.entity;
import javax.validation.constraints.NotNull;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class TbUserRole {
@TableId(type = IdType.AUTO)
private Long id;
@TableField("user_id")
@NotNull
private Long userId;
@TableField("role_id")
@NotNull
private Long roleId;
}
JwtAccessDeniedHandler
package com.stu.manage.demo.exception;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
/**
* @author cdc
* @description AccessDeineHandler 用来解决认证过的用户访问无权限资源时的异常
*/
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
/**
* 当用户尝试访问需要权限才能的REST资源而权限不足的时候,
* 将调用此方法发送403响应以及错误信息
*/
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
accessDeniedException = new AccessDeniedException("无权限操作!!");
response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
/*response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
ServletOutputStream outputStream = response.getOutputStream();
Result result = ResultUtil.error(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
outputStream.write(JSONUtils.toJSONString(result).getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();*/
}
}
JwtAuthenticationEntryPoint
package com.stu.manage.demo.exception;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
/**
* @author cdc
* @description AuthenticationEntryPoint 用来解决匿名用户访问需要权限才能访问的资源时的异常
*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
/**
* 当用户尝试访问需要权限才能的REST资源而不提供Token或者Token错误或者过期时,
* 将调用此方法发送401响应以及错误信息
*/
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}
}
JWTAuthenticationFilter
package com.stu.manage.demo.filter;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.stu.manage.demo.constants.SecurityConstants;
import com.stu.manage.demo.dto.LoginRequest;
import com.stu.manage.demo.entity.JwtUser;
import com.stu.manage.demo.utils.JwtTokenUtils;
// 认证过滤器
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final StringRedisTemplate stringRedisTemplate;
private ThreadLocal rememberMe = new ThreadLocal<>();
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager, StringRedisTemplate stringRedisTemplate) {
this.authenticationManager = authenticationManager;
this.stringRedisTemplate = stringRedisTemplate;
// 设置登录请求的 URL
super.setFilterProcessesUrl(SecurityConstants.AUTH_LOGIN_URL);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
ObjectMapper objectMapper = new ObjectMapper();
try {
// 从输入流中获取到登录的信息
LoginRequest loginRequest = objectMapper.readValue(request.getInputStream(), LoginRequest.class);
rememberMe.set(loginRequest.getRememberMe());
// 这部分和attemptAuthentication方法中的源码是一样的,
// 只不过由于这个方法源码的是把用户名和密码这些参数的名字是死的,所以我们重写了一下
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(), loginRequest.getPassword());
return authenticationManager.authenticate(authRequest);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* 如果验证成功,就生成token并返回
*/
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authentication) {
JwtUser jwtUser = (JwtUser) authentication.getPrincipal();
List roles = jwtUser.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
// 创建 Token
String token = JwtTokenUtils.createToken(jwtUser.getUsername(), jwtUser.getId().toString(), roles, rememberMe.get());
stringRedisTemplate.opsForValue().set(jwtUser.getId().toString(), token);
// Http Response Header 中返回 Token
response.setHeader(SecurityConstants.TOKEN_HEADER, token);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage());
}
}
JwtAuthorizationFilter
package com.stu.manage.demo.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import com.stu.manage.demo.constants.SecurityConstants;
import com.stu.manage.demo.utils.JwtTokenUtils;
import io.jsonwebtoken.JwtException;
import lombok.extern.slf4j.Slf4j;
/**
* 授权过滤器
* @description 过滤器处理所有HTTP请求,并检查是否存在带有正确令牌的Authorization标头。例如,如果令牌未过期或签名密钥正确。
*/
@Slf4j
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
private final StringRedisTemplate stringRedisTemplate;
public JwtAuthorizationFilter(AuthenticationManager authenticationManager, StringRedisTemplate stringRedisTemplate) {
super(authenticationManager);
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
String token = request.getHeader(SecurityConstants.TOKEN_HEADER);
if (token == null || !token.startsWith(SecurityConstants.TOKEN_PREFIX)) {
SecurityContextHolder.clearContext();
chain.doFilter(request, response);
return;
}
String tokenValue = token.replace(SecurityConstants.TOKEN_PREFIX, "");
UsernamePasswordAuthenticationToken authentication = null;
try {
String previousToken = stringRedisTemplate.opsForValue().get(JwtTokenUtils.getId(tokenValue));
if (!token.equals(previousToken)) {
SecurityContextHolder.clearContext();
chain.doFilter(request, response);
return;
}
authentication = JwtTokenUtils.getAuthentication(tokenValue);
} catch (JwtException e) {
logger.error("Invalid jwt : " + e.getMessage());
}
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
}
TbPermissionMapper
package com.stu.manage.demo.mapper;
import java.util.List;
import org.springframework.stereotype.Repository;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.stu.manage.demo.entity.TbPermission;
import com.stu.manage.demo.entity.TbUser;
@Repository
public interface TbPermissionMapper extends BaseMapper {
List findByUser(TbUser tbUser);
}
TbPermissionMapper.xml
id, parent_id, `name`, enname, url, description, created, updated
TbUserMapper.xml
id, username, `password`, phone, email, created, updated
Result
package com.stu.manage.demo.result;
import lombok.Data;
@Data
public class Result {
private Integer code;
private String msg;
private T data;
public Result() {
super();
}
public Result(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
@Override
public String toString() {
return "Result{" +
"code=" + code +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
}
ResultEnum
package com.stu.manage.demo.result;
public enum ResultEnum {
//可自行定义,与前端交互
UNKNOWN_ERROR(-1,"未知错误"),
SUCCESS(200,"成功"),
STUDENT_NOT_EXIST(1,"学生不存在"),
STUDENT_IS_EXISTS(2,"学生已存在"),
DATA_IS_NULL(3,"数据为空"),
DELETE_FAIL(5,"删除失败"),
UPDATE_FAIL(6,"更新失败"),
VERIFY_FAIL(7,"验证失败")
;
private Integer code;
private String msg;
ResultEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
ResultUtil
package com.stu.manage.demo.result;
public class ResultUtil {
/**成功且带数据**/
public static Result success(Object object){
Result result = new Result();
result.setCode(ResultEnum.SUCCESS.getCode());
result.setMsg(ResultEnum.SUCCESS.getMsg());
result.setData(object);
return result;
}
/**成功但不带数据**/
public static Result success(){
return success(null);
}
/**失败**/
public static Result error(Integer code,String msg){
Result result = new Result();
result.setCode(code);
result.setMsg(msg);
return result;
}
}
LoginServiceImpl
package com.stu.manage.demo.service.impl;
import java.util.List;
import java.util.stream.Collectors;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.stu.manage.demo.dto.LoginRequest;
import com.stu.manage.demo.entity.TbPermission;
import com.stu.manage.demo.entity.TbUser;
import com.stu.manage.demo.mapper.LoginMapper;
import com.stu.manage.demo.mapper.TbUserMapper;
import com.stu.manage.demo.service.ITbPermissionService;
import com.stu.manage.demo.service.ITbUserService;
import com.stu.manage.demo.service.LoginService;
import com.stu.manage.demo.utils.JwtTokenUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
public class LoginServiceImpl implements LoginService {
private static final String TAG = LoginServiceImpl.class.getName();
// private static final Logger logger = Logger.getLogger(LoginServiceImpl.class.getName());
@Autowired
private LoginMapper loginMapper;
@Autowired
private ITbUserService tbUserService;
@Autowired
private ITbPermissionService permissionService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public String getAdmin(String name){
return loginMapper.getAdmin(name);
}
@Override
public String createToken(LoginRequest loginRequest) {
TbUserMapper mapper = tbUserService.getMapper();
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userName", loginRequest.getUsername());
TbUser tbUser = mapper.selectOne(queryWrapper);
if (!tbUserService.check(loginRequest.getPassword(), tbUser.getPassWord())) {
throw new BadCredentialsException("The username or password is not correct");
}
List permissions = permissionService.findByUser(tbUser).stream().map(TbPermission::getEnname).collect(Collectors.toList());
String token = JwtTokenUtils.createToken(tbUser.getUserName(), tbUser.getId().toString(), permissions, true);
log.info(TAG, "{} createToken is [}", tbUser.getUserName(), token);
//logger.info("{} createToken is [}", tbUser.getUserName(), token);
stringRedisTemplate.opsForValue().set(tbUser.getId().toString(), token);
return token;
}
}
CurrentUserUtils
package com.stu.manage.demo.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import com.stu.manage.demo.entity.TbUser;
import com.stu.manage.demo.service.ITbUserService;
import lombok.RequiredArgsConstructor;
/**
* @description 获取当前请求的用户
*/
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class CurrentUserUtils {
private final ITbUserService tbUserService;
public TbUser getCurrentUser() {
return tbUserService.findByName(getCurrentUserName());
}
private String getCurrentUserName() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() != null) {
return (String) authentication.getPrincipal();
}
return null;
}
}
JwtTokenUtils
package com.stu.manage.demo.utils;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import javax.crypto.SecretKey;
import javax.xml.bind.DatatypeConverter;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import com.stu.manage.demo.constants.SecurityConstants;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
public class JwtTokenUtils {
/**
* 生成足够的安全随机密钥,以适合符合规范的签名
*/
private static final byte[] API_KEY_SECRET_BYTES = DatatypeConverter.parseBase64Binary(SecurityConstants.JWT_SECRET_KEY);
private static final SecretKey SECRET_KEY = Keys.hmacShaKeyFor(API_KEY_SECRET_BYTES);
public static String createToken(String username, String id, List permissions, boolean isRememberMe) {
long expiration = isRememberMe ? SecurityConstants.EXPIRATION_REMEMBER : SecurityConstants.EXPIRATION;
final Date createdDate = new Date();
final Date expirationDate = new Date(createdDate.getTime() + expiration * 1000);
String tokenPrefix = Jwts.builder()
.setHeaderParam("type", SecurityConstants.TOKEN_TYPE)
.signWith(SECRET_KEY, SignatureAlgorithm.HS256) // 密钥
.claim(SecurityConstants.ROLE_CLAIMS, String.join(",", permissions)) // 载荷
.setId(id) // jwt唯一标识
.setIssuer("SnailClimb") //jwt的签发者
.setIssuedAt(createdDate) //jwt的签发标识
.setSubject(username) //sub(Subject):代表这个JWT的主体,是一个json格式的字符串,作为什么用户的唯一标志
.setExpiration(expirationDate) // 过期时间
.compact(); //压缩为xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx这样的jwt
return SecurityConstants.TOKEN_PREFIX + tokenPrefix; // 添加 token 前缀 "Bearer ";
}
public static String getId(String token) {
Claims claims = getClaims(token);
return claims.getId();
}
public static UsernamePasswordAuthenticationToken getAuthentication(String token) {
Claims claims = getClaims(token);
List authorities = getAuthorities(claims);
String userName = claims.getSubject();
return new UsernamePasswordAuthenticationToken(userName, token, authorities);
}
private static List getAuthorities(Claims claims) {
String role = (String) claims.get(SecurityConstants.ROLE_CLAIMS);
return Arrays.stream(role.split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
private static Claims getClaims(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
}
}
FullName
package com.stu.manage.demo.validator;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
/**
* 功能描述:自定义校验FullName合法性的注解
*/
@Documented
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = FulleNameValidator.class)
public @interface FullName {
String message() default "姓名格式错误";
Class[] groups() default {};
Class[] payload() default {};
}
FulleNameValidator
package com.stu.manage.demo.validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class FulleNameValidator implements ConstraintValidator {
/**
* 2 - 15 位字母 / 数字 / 简繁体字
*/
private static final String FULL_NAME_REG_EXP = "(?!.*\\s$)((?=\\S)(?![0-9]+$)[\\u4E00-\\u9FA5A-Za-z0-9. ' ]{2,15})";
@Override
public boolean isValid(String fullNameStr, ConstraintValidatorContext context) {
if (fullNameStr == null) {
return true;
}
log.info("fullName is {}", fullNameStr);
return fullNameStr.matches(FULL_NAME_REG_EXP);
}
}
application.properties
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase?useSSL=false&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
#spring.datasource.initialSize=20
#spring.datasource.minIdle=50
#spring.datasource.maxActive=500
server.port=8888
#mybatis plus
mybatis-plus.mapper-locations=classpath:mapper/*.xml
mybatis-plus.type-aliases-package=com.stu.manage.demo.entity
logging.level.com.stu.manage.demo.mapper = debug
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.timeout=10000
spring.redis.database=0
spring.redis.jedis.pool.max-active=10
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=0
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.4.0
com.stu-mange
demo
0.0.1-SNAPSHOT
demo
Demo project for Spring Boot
1.8
0.10.7
29.0-jre
4.13.1
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-validation
org.springframework.boot
spring-boot-starter-security
org.springframework.security
spring-security-test
test
org.springframework.boot
spring-boot-starter-jdbc
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-web-services
org.springframework.boot
spring-boot-starter-webflux
org.springframework.boot
spring-boot-devtools
runtime
true
org.postgresql
postgresql
runtime
org.springframework.boot
spring-boot-starter-test
test
mysql
mysql-connector-java
runtime
io.projectreactor
reactor-test
test
com.microsoft.sqlserver
mssql-jdbc
runtime
org.apache.httpcomponents
httpclient
4.5
org.apache.httpcomponents
httpmime
4.5
org.apache.httpcomponents
httpcore
4.4.1
com.alibaba
fastjson
1.2.17
com.baomidou
mybatis-plus-boot-starter
3.4.0
com.alibaba
druid
1.1.9
io.springfox
springfox-swagger2
2.9.2
io.springfox
springfox-swagger-ui
2.9.2
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-data-redis
io.jsonwebtoken
jjwt-api
${jwt.version}
io.jsonwebtoken
jjwt-impl
${jwt.version}
runtime
io.jsonwebtoken
jjwt-jackson
${jwt.version}
runtime
com.google.guava
guava
${guava.version}
junit
junit
${junit.version}
test
org.springframework.boot
spring-boot-maven-plugin
该记录引用项目:https://github.com/Snailclimb/spring-security-jwt-guide、http://t.zoukankan.com/snailclimb-p-11631890.html