基于springboot和jwt的预约系统

基于springboot和jwt的预约系统

1、依赖

 <dependencies><dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-webartifactId>dependency><dependency><groupId>mysqlgroupId><artifactId>mysql-connector-javaartifactId><scope>runtimescope>dependency><dependency><groupId>org.projectlombokgroupId><artifactId>lombokartifactId><optional>trueoptional>dependency><dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-testartifactId><scope>testscope>dependency><dependency><groupId>com.baomidougroupId><artifactId>mybatis-plus-boot-starterartifactId><version>3.4.3.1version>dependency><dependency><groupId>com.h2databasegroupId><artifactId>h2artifactId><scope>runtimescope>dependency><dependency><groupId>com.baomidougroupId><artifactId>mybatis-plus-generatorartifactId><version>3.4.1version>dependency><dependency><groupId>org.apache.velocitygroupId><artifactId>velocity-engine-coreartifactId><version>2.0version>dependency><dependency><groupId>io.springfoxgroupId><artifactId>springfox-swagger-uiartifactId><version>2.4.0version>dependency><dependency><groupId>io.springfoxgroupId><artifactId>springfox-swagger2artifactId><version>2.4.0version>dependency><dependency><groupId>cn.hutoolgroupId><artifactId>hutool-allartifactId><version>5.7.4version>dependency><dependency><groupId>junitgroupId><artifactId>junitartifactId>dependency><dependency><groupId>io.jsonwebtokengroupId><artifactId>jjwtartifactId><version>0.9.1version>dependency><dependency><groupId>javax.xml.bindgroupId><artifactId>jaxb-apiartifactId><version>2.3.0version>dependency><dependency><groupId>com.sun.xml.bindgroupId><artifactId>jaxb-implartifactId><version>2.3.0version>dependency><dependency><groupId>com.sun.xml.bindgroupId><artifactId>jaxb-coreartifactId><version>2.3.0version>dependency><dependency><groupId>javax.activationgroupId><artifactId>activationartifactId><version>1.1.1version>dependency><dependency><groupId>com.alibabagroupId><artifactId>easyexcelartifactId><version>2.1.1version>dependency><dependency><groupId>org.apache.poigroupId><artifactId>poiartifactId><version>3.17version>dependency><dependency><groupId>org.apache.poigroupId><artifactId>poi-ooxmlartifactId><version>3.17version>dependency><dependency><groupId>com.usthe.surenessgroupId><artifactId>sureness-coreartifactId><version>1.0.3version>dependency>dependencies>

2、建表 使用mybatisplus代码生成器


import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;public class Main {public static void main(String[] args) {//创建AutoGenerator对象AutoGenerator autoGenerator = new AutoGenerator();//数据源DataSourceConfig dataSourceConfig = new DataSourceConfig();dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/yuyue?useUnicode=true&useSSL=false&characterEncoding=UTF-8");dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");dataSourceConfig.setUsername("root");dataSourceConfig.setPassword("123456");autoGenerator.setDataSource(dataSourceConfig);//全局配置GlobalConfig globalConfig = new GlobalConfig();//System.getProperty("user.dir")工程绝对路径globalConfig.setOutputDir(System.getProperty("user.dir") + "/src/main/java");globalConfig.setAuthor("william");//创建后不打开文件globalConfig.setOpen(false);autoGenerator.setGlobalConfig(globalConfig);//    包信息PackageConfig packageConfig = new PackageConfig();packageConfig.setParent("com.william");//各层存放位置packageConfig.setController("controller");packageConfig.setService("service");packageConfig.setServiceImpl("service.impl");packageConfig.setEntity("entity");packageConfig.setMapper("mapper");autoGenerator.setPackageInfo(packageConfig);//    配置策略StrategyConfig strategyConfig = new StrategyConfig();//entity生成后自动使用lombok注解strategyConfig.setEntityLombokModel(true);//开启下划线转驼峰strategyConfig.setNaming(NamingStrategy.underline_to_camel);autoGenerator.setStrategy(strategyConfig);//开始生成autoGenerator.execute();}
}

然后在启动类加上@MapperScan("com.william.mapper")

# application.yml
server:port: 8081spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: 123456url: jdbc:mysql://localhost:3306/yuyue?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8# 环境设置:dev、test、prodprofiles:active: dev#mybatis日志
logging:level:root: warn#配置日志级别
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplmapper-locations: classpath*:**/mapper/xml/*.xmltype-enums-package: com.william.enums

3、统一结果返回

public interface ResultCode {public static Integer SUCCESS = 200;public static Integer ERROR = 400;
}
package com.william.commonUtils;import lombok.Data;import java.util.HashMap;
import java.util.Map;@Data
public class R {private Boolean success;private Integer code;private String msg;private Map<String, Object> data = new HashMap<>();private R() {}public static R ok() {R r = new R();r.setCode(ResultCode.SUCCESS);r.setSuccess(true);r.setMsg("成功");return r;}public static R error() {R r = new R();r.setCode(ResultCode.ERROR);r.setSuccess(false);r.setMsg("失败");return r;}public R success(Boolean success) {this.setSuccess(success);return this;}public R message(String message) {this.setMsg(message);return this;}public R code(Integer code) {this.setCode(code);return this;}public R data(String key, Object value) {this.data.put(key, value);return this;}public R data(Map<String, Object> map) {this.setData(map);return this;}
}

4、全局异常处理

/*** 自定义异常类*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyException extends RuntimeException {/*** 状态码*/private Integer code;/*** 异常信息*/private String msg;
}
/*** 异常处理*/
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {/*** 全局异常处理** @param e* @return*/@ExceptionHandler(Exception.class)@ResponseBodypublic R error(Exception e) {e.printStackTrace();return R.error().message("执行了全局异常处理:");}/*** 特定异常处理** @param e* @return*/@ExceptionHandler(ArithmeticException.class)@ResponseBodypublic R error(ArithmeticException e) {e.printStackTrace();return R.error().message("执行了特定异常");}/*** 自定义异常处理** @param e* @return*/@ExceptionHandler(MyException.class)@ResponseBodypublic R error(MyException e) {e.printStackTrace();log.error(e.getMsg());return R.error().code(e.getCode()).message(e.getMsg());}
}

5、通用枚举

public enum  AppointStatusEnum {/*** 用户登录状态的枚举类*/EFFECTIVE(0,"effective"),CANCELLED(1,"cancelled"),EXPIRED(2,"expired");@EnumValueprivate Integer code;@JsonValueprivate String msg;AppointStatusEnum(Integer code, String msg) {this.code = code;this.msg = msg;}
}
  // 修改entity中属性的类型/*** 预约状态*/private AppointStatusEnum status;

6、字段自动填充

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {@Overridepublic void insertFill(MetaObject metaObject) {//属性名称,不是字段名称this.setFieldValByName("createTime", new Date(), metaObject);this.setFieldValByName("updateTime", new Date(), metaObject);}@Overridepublic void updateFill(MetaObject metaObject) {this.setFieldValByName("updateTime", new Date(), metaObject);}
}
/*** 创建时间*/
@TableField(fill = FieldFill.INSERT)
private Date createTime;/*** 更新时间*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;

7、逻辑删除

/***     开启逻辑删除*     未删除:1 已删除:0*/
@TableLogic(value = "1", delval = "0")
private Integer deleted;

8、编写业务代码

controller

@CrossOrigin
@RestController
@RequestMapping("/xxx")

分页+条件+联表查询

// mybatisplus分页插件配置
@Configuration
@MapperScan("com.william.mapper")
public class MybatisPlusConfig {// 最新版@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}
}
// VO
@Data
public class PageQueryVO {/*** 学号*/public String studentId;/*** 实验室号*/public String roomId;/*** 用户名*/public String userName;/*** 预约状态*/public Integer status;
}
	// controller@ApiOperation(value = "分页+条件+联表查询预约信息")@PostMapping("/getAppointPage/{curPage}/{pageSize}")public R getAppointPage(@PathVariable Integer curPage, @PathVariable Integer pageSize, @RequestBody(required = false) PageQueryVO pageQueryVO) {Page<AppointPageVO> page = appointmentService.getAppointPageVO(curPage, pageSize, pageQueryVO);if (page != null) {return R.ok().data("page", page).message("查询成功");} else {return R.error().message("查询失败");}}
// service
public interface IAppointmentService extends IService<Appointment> {Page<AppointPageVO> getAppointPageVO(Integer curPage, Integer pageSize, PageQueryVO pageQueryVO);
}// service.impl
@Service
public class AppointmentServiceImpl extends ServiceImpl<AppointmentMapper, Appointment> implements IAppointmentService {@Overridepublic Page<AppointPageVO> getAppointPageVO(Integer curPage, Integer pageSize, PageQueryVO pageQueryVO) 	{Page<AppointPageVO> page = new Page<>(curPage, pageSize);return  baseMapper.pageList(page, pageQueryVO);}
}
// mapper
public interface AppointmentMapper extends BaseMapper<Appointment> {Page<AppointPageVO> pageList(@Param("page") Page<AppointPageVO> page, @Param("pageQueryVO") PageQueryVO pageQueryVO);
}
// mapper.xml

DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.william.mapper.AppointmentMapper"><select id="pageList" resultType="com.william.entity.vo.AppointPageVO">select a.id,a.room_id,a.date,a.time,a.status,u.user_name,u.student_idfrom appointment a, `user` u<where>a.user_id = u.user_idand u.deleted = 1<if test="pageQueryVO.studentId != null and !(pageQueryVO.studentId).equals('')">and u.student_id=#{pageQueryVO.studentId}if><if test="pageQueryVO.roomId != null and !(pageQueryVO.roomId).equals('')">and a.room_id=#{pageQueryVO.roomId}if><if test="pageQueryVO.status != null and !(pageQueryVO.status).equals('')">and a.status=#{pageQueryVO.status}if>where>order by a.id descselect>
mapper>

9、swagger2

@Configuration
@EnableSwagger2
public class SwaggerConfig {//默认文档地址为 http://localhost:port/swagger-ui.html@Beanpublic Docket createRestApi() {return new Docket(DocumentationType.SWAGGER_2)          //指定Api类型为Swagger2.apiInfo(apiInfo())                             //指定文档汇总信息.select().apis(RequestHandlerSelectors.basePackage("com.william.controller")) //指定controller包路径.paths(PathSelectors.any())                     //指定展示所有controller.build();}private ApiInfo apiInfo() {//返回一个apiinforeturn new ApiInfoBuilder().title("预约系统")                                       //文档页标题.description("api文档")                                       // 详细信息.version("1.0.0")                                           // 文档版本号.termsOfServiceUrl("https://www.baidu.com")                  //网站地址.build();}
}

10、登录功能

MD5

/*** MD5非对称加密工具类*/
public final class MD5 {public static String encrypt(String strSrc) {try {char hexChars[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8','9', 'a', 'b', 'c', 'd', 'e', 'f'};byte[] bytes = strSrc.getBytes();MessageDigest md = MessageDigest.getInstance("MD5");md.update(bytes);bytes = md.digest();int j = bytes.length;char[] chars = new char[j * 2];int k = 0;for (int i = 0; i < bytes.length; i++) {byte b = bytes[i];chars[k++] = hexChars[b >>> 4 & 0xf];chars[k++] = hexChars[b & 0xf];}return new String(chars);} catch (NoSuchAlgorithmException e) {e.printStackTrace();throw new RuntimeException("MD5加密出错!!+" + e);}}}

JwtUtil

public class JwtUtil {private static String signature = "密钥";private static String tokenName = "从request.header中取token的名称";/*** 根据用户信息生成JwtToken** @param user 用户信息* @return token*/public static String createToken(User user) {JwtBuilder jwtBuilder = Jwts.builder();return jwtBuilder// header.setHeaderParam("typ", "JWT").setHeaderParam("alg", "HS256")// payload.claim("password", user.getPassword())// userId存在Subject中.setSubject(user.getUserId().toString())// 24H过期.setExpiration(DateUtil.tomorrow()).setId(UUID.randomUUID().toString())// signature.signWith(SignatureAlgorithm.HS256, signature).compact();}/*** 解析request.header中的token获取用户id并返回** @param request* @return*/public static String getUserIdByJwtToken(HttpServletRequest request) {String jwtToken = request.getHeader(tokenName);if (StrUtil.hasEmpty(jwtToken)) {return "";}JwtParser parser = Jwts.parser();Jws<Claims> claimsJws = parser.setSigningKey(signature).parseClaimsJws(jwtToken);Claims jwsBody = claimsJws.getBody();// 加密时Subject中存的是用户idreturn jwsBody.getSubject();}/*** 解析token返回body*/public static Claims getBodyByJwtToken(String jwtToken) {JwtParser parser = Jwts.parser();Jws<Claims> claimsJws = parser.setSigningKey(signature).parseClaimsJws(jwtToken);// 加密时Subject中存的是用户idreturn claimsJws.getBody();}
}

service.impl

public String login(User user) {String studentId = user.getStudentId();String password = user.getPassword();// 非空校验if (StrUtil.hasEmpty(studentId) || StrUtil.hasEmpty(password)) {throw new MyException(ResultCode.ERROR, "登陆失败");}// 根据学号查数据QueryWrapper<User> wrapper = new QueryWrapper<User>();wrapper.eq("student_id", studentId);User userInBase = baseMapper.selectOne(wrapper);// 判断学号是否存在if (userInBase == null) {throw new MyException(ResultCode.ERROR, "登陆失败,用户不存在");}//判断密码是否符合  前端传来的密码通过MD5加密后和数据库中比较if (!MD5.encrypt(password).equals(userInBase.getPassword())) {throw new MyException(ResultCode.ERROR, "登录失败,密码错误");}// 登录成功,状态设为在线并返回tokenuserInBase.setStatus(UserStatusEnum.ACTIVE);baseMapper.updateById(userInBase);return JwtUtil.createToken(userInBase);
}

根据token获取用户信息

@ApiOperation(value = "根据token返回用户信息和权限", notes = "token在请求中的header中携带")
@GetMapping("/getUserInfo")
public R getUserInfo(HttpServletRequest request) {String userId = JwtUtil.getUserIdByJwtToken(request);User user = userService.getById(userId);if (user != null) {user.setStatus(UserStatusEnum.ACTIVE);userService.updateById(user);Role role = roleService.getById(user.getRoleId());LinkedList<String> rolesList = new LinkedList<>();rolesList.add(role.getRoleKey());// 前端根据返回的roles动态生成路由return R.ok().data("userInfo", user).data("roles", rolesList).message("获取用户信息成功");} else {return R.error().message("获取用户信息失败");}
}

11、使用excel导入用户

@Data
public class UserExcel {/*** 姓名*/@ExcelProperty("姓名")private String userName;/*** 学号*/@ExcelProperty("学号")private String studentId;/*** 学院*/@ExcelProperty("学院")private String college;/*** 专业班级*/@ExcelProperty("专业班级")private String major;
}
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去/*** easyExcel监听器* @author william*/
public class UploadDataListener extends AnalysisEventListener<UserExcel> {private static final Logger LOGGER = LoggerFactory.getLogger(UploadDataListener.class);/*** 每隔10条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收*/private static final int BATCH_COUNT = 10;List<UserExcel> list = new ArrayList<UserExcel>();/*** 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。*/private  IUserService userService;/*** 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来*/public UploadDataListener(IUserService userService) {this.userService = userService;}/*** 这个每一条数据解析都会来调用*/@Overridepublic void invoke(UserExcel data, AnalysisContext context) {LOGGER.info("解析到一条数据:{}", JSONUtil.toJsonStr(data));// 判断学号是否是已经存在数据库中的String studentId = data.getStudentId();QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.eq("student_id", studentId);User one = userService.getOne(wrapper);if (one == null){list.add(data);} else {LOGGER.error("学号为{}的数据已存在!", studentId);}// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOMif (list.size() >= BATCH_COUNT) {saveData();// 存储完成清理 listlist.clear();}}/*** 所有数据解析完成了 都会来调用** @param context*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {// 这里也要保存数据,确保最后遗留的数据也存储到数据库saveData();LOGGER.info("所有数据解析完成!");}/*** 加上存储数据库*/private void saveData() {LOGGER.info("{}条数据,开始存储数据库!", list.size());userService.saveBatch(list);LOGGER.info("存储数据库成功!");}
}
@ApiOperation(value = "通过excel导入学生信息")
@PostMapping("/addUserByExcel")
public R addUserByExcel(MultipartFile file) throws IOException {userService.saveUsers(file, userService);return R.ok().message("导入成功");
}
public void saveUsers(MultipartFile file, IUserService userService) {try {//获取文件输入流InputStream inputStream = file.getInputStream();// 调用方法读取文件EasyExcel.read(inputStream, UserExcel.class, new UploadDataListener(userService)).sheet().doRead();} catch (IOException e) {e.printStackTrace();}
}

12、logback日志

logback-spring.xml


<configuration  scan="true" scanPeriod="10 seconds"><contextName>logbackcontextName><property name="log.path" value="log" /><property name="CONSOLE_LOG_PATTERN"value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><filter class="ch.qos.logback.classic.filter.ThresholdFilter"><level>DEBUGlevel>filter><encoder><Pattern>${CONSOLE_LOG_PATTERN}Pattern><charset>UTF-8charset>encoder>appender><appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${log.path}/log_info.logfile><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern><charset>UTF-8charset>encoder><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.logfileNamePattern><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MBmaxFileSize>timeBasedFileNamingAndTriggeringPolicy><maxHistory>15maxHistory>rollingPolicy><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>INFOlevel><onMatch>ACCEPTonMatch><onMismatch>DENYonMismatch>filter>appender><appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${log.path}/log_warn.logfile><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern><charset>UTF-8charset> encoder><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.logfileNamePattern><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MBmaxFileSize>timeBasedFileNamingAndTriggeringPolicy><maxHistory>15maxHistory>rollingPolicy><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>warnlevel><onMatch>ACCEPTonMatch><onMismatch>DENYonMismatch>filter>appender><appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${log.path}/log_error.logfile><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern><charset>UTF-8charset> encoder><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.logfileNamePattern><timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"><maxFileSize>100MBmaxFileSize>timeBasedFileNamingAndTriggeringPolicy><maxHistory>15maxHistory>rollingPolicy><filter class="ch.qos.logback.classic.filter.LevelFilter"><level>ERRORlevel><onMatch>ACCEPTonMatch><onMismatch>DENYonMismatch>filter>appender><springProfile name="dev"><logger name="com.william" level="INFO" /><root level="INFO"><appender-ref ref="CONSOLE" /><appender-ref ref="INFO_FILE" /><appender-ref ref="WARN_FILE" /><appender-ref ref="ERROR_FILE" />root>springProfile><springProfile name="pro"><root level="INFO"><appender-ref ref="CONSOLE" /><appender-ref ref="DEBUG_FILE" /><appender-ref ref="INFO_FILE" /><appender-ref ref="ERROR_FILE" /><appender-ref ref="WARN_FILE" />root>springProfile>configuration>

在全局异常处理类上加

@ControllerAdvice
@Slf4j

13、sureness鉴权

根据角色分配资源(RESTapi)

image-20210803120159125

@Component
public class DatabaseAccountProvider implements SurenessAccountProvider {@Resourceprivate IUserService userService;@Overridepublic SurenessAccount loadAccount(String appId) {return userService.loadAccount(appId);}
}
    // userService.impl@Overridepublic SurenessAccount loadAccount(String appId) {User user = baseMapper.selectById(appId);if (user != null) {DefaultAccount.Builder accountBuilder = DefaultAccount.builder(appId).setPassword(user.getPassword()).setDisabledAccount(user.getDeleted() == 0);Role role = roleMapper.selectById(user.getRoleId());if (role != null) {List<String> roles = new LinkedList<>();roles.add(role.getRoleKey());accountBuilder.setOwnRoles(roles);}return accountBuilder.build();} else {return null;}}
public class RefreshExpiredTokenException extends SurenessAuthenticationException {public RefreshExpiredTokenException(String message) {super(message);}
}
public class PasswdSubjectCreator implements SubjectCreate {private static final String jwtName = "token名字";@Overridepublic boolean canSupportSubject(Object context) {// define which request can be accessif (context instanceof HttpServletRequest) {String token = ((HttpServletRequest) context).getHeader(jwtName);return token != null;} else {return false;}}@Overridepublic Subject createSubject(Object context) {// create PasswordSubject from request// 从jwt中拿用户名和密码做鉴权String token = ((HttpServletRequest) context).getHeader(jwtName);Claims body = JwtUtil.getBodyByJwtToken(token);String username = body.getSubject();String password = (String) body.get("password");String remoteHost = ((HttpServletRequest) context).getRemoteHost();String requestUri = ((HttpServletRequest) context).getRequestURI();String requestType = ((HttpServletRequest) context).getMethod();String targetUri = requestUri.concat("===").concat(requestType).toLowerCase();return PasswordSubject.builder(username, password).setRemoteHost(remoteHost).setTargetResource(targetUri).build();}
}
@Configuration
public class SurenessConfiguration {@BeanProcessorManager processorManager(SurenessAccountProvider accountProvider) {//    处理器processor初始化List<Processor> processorList = new LinkedList<>();//     使用了默认的支持NoneSubject的处理器NoneProcessorNoneProcessor noneProcessor = new NoneProcessor();processorList.add(noneProcessor);// 使用了默认的支持JwtSubject的处理器JwtProcessorJwtProcessor jwtProcessor = new JwtProcessor();processorList.add(jwtProcessor);// 使用了默认的支持PasswordSubject的处理器PasswordProcessorPasswordProcessor passwordProcessor = new PasswordProcessor();// 这里注意,PasswordProcessor需要对用户账户密码验证,所以其需要账户信息提供者来给他提供想要的账户数据,// 这里的 SurenessAccountProvider accountProvider bean就是这个账户数据提供源,// 其实现bean是上面讲到的 DatabaseAccountProvider bean,即数据库实现的账户数据提供者。passwordProcessor.setAccountProvider(accountProvider);processorList.add(passwordProcessor);return new DefaultProcessorManager(processorList);}@BeanTreePathRoleMatcher pathRoleMatcher() {// 这里的PathTreeProvider databasePathTreeProvider 就是通过数据库来提供资源权限配置信息bean实例// 下面我们再实例化一个通过文件sureness.yml提供资源权限配置信息的提供者PathTreeProvider documentPathTreeProvider = new DocumentPathTreeProvider();// 下面我们再实例化一个通过注解形式@RequiresRoles @WithoutAuth提供资源权限配置信息的提供者AnnotationPathTreeProvider annotationPathTreeProvider = new AnnotationPathTreeProvider();annotationPathTreeProvider.setScanPackages(Collections.singletonList("com.william.controller"));// 设置注解扫描包路径,也就是你提供api的controller路径 annotationPathTreeProvider.setScanPackages(Collections.singletonList("com.usthe.sureness.sample.tom.controller"));// 开始初始化资源权限匹配器,我们可以把上面三种提供者都加入到匹配器中为其提供资源权限数据,匹配器中的数据就是这三个提供者提供的数据集合。tDefaultPathRoleMatcher pathRoleMatcher = new DefaultPathRoleMatcher();pathRoleMatcher.setPathTreeProviderList(Arrays.asList(documentPathTreeProvider,annotationPathTreeProvider));// 使用资源权限配置数据来建立对应的匹配树pathRoleMatcher.buildTree();return pathRoleMatcher;}@BeanSubjectFactory subjectFactory() {// 我们之前知道了可以有各种Processor来处理对应的Jwt,那这Subject怎么得到呢,就需要不同的SubjectCreator来根据请求信息创建对应的Subject对象供之后的流程使用SubjectFactory subjectFactory = new SurenessSubjectFactory();// 这里我们注册我们需要的SubjectCreatorsubjectFactory.registerSubjectCreator(Arrays.asList(// 注意! 强制必须首先添加一个 noSubjectCreatornew NoneSubjectServletCreator(),// 注册用来创建PasswordSubject的creatornew BasicSubjectServletCreator(),// 注册用来创建JwtSubject的creatornew JwtSubjectServletCreator(),// 当然你可以自己实现一个自定义的creator,实现SubjectCreate接口即可new PasswdSubjectCreator()));return subjectFactory;}@BeanSurenessSecurityManager securityManager(ProcessorManager processorManager,TreePathRoleMatcher pathRoleMatcher, SubjectFactory subjectFactory) {// 我们把上面初始化好的配置bean整合到一起初始化surenessSecurityManagerSurenessSecurityManager securityManager = SurenessSecurityManager.getInstance();// 设置资源权限匹配者securityManager.setPathRoleMatcher(pathRoleMatcher);// 设置subject创建工厂securityManager.setSubjectFactory(subjectFactory);// 设置处理器processor管理者securityManager.setProcessorManager(processorManager);return securityManager;}
}

过滤器

在启动类中开启@ServletComponentScan

@Order(1)
@WebFilter(filterName = "SurenessFilter", urlPatterns = "/*", asyncSupported = true)
public class SurenessFilter implements Filter {/** logger **/private static final Logger logger = LoggerFactory.getLogger(SurenessFilter.class);@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {try {SubjectSum subject = SurenessSecurityManager.getInstance().checkIn(servletRequest);// You can consider using SurenessContextHolder to bind subject in threadLocal// if bind, please remove it when endif (subject != null) {SurenessContextHolder.bindSubject(subject);}} catch (IncorrectCredentialsException | UnknownAccountException | ExpiredCredentialsException e1) {logger.debug("this request account info is illegal, {}", e1.getMessage());responseWrite(ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Username or password is incorrect or expired"), servletResponse);return;} catch (DisabledAccountException | ExcessiveAttemptsException e2 ) {logger.debug("the account is disabled, {}", e2.getMessage());responseWrite(ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Account is disabled"), servletResponse);return;} catch (RefreshExpiredTokenException e4) {logger.debug("this account credential token is expired, return refresh value");Map<String, String> refreshTokenMap = Collections.singletonMap("refresh-token", e4.getMessage());responseWrite(ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(refreshTokenMap), servletResponse);return;} catch (UnauthorizedException e5) {logger.debug("this account can not access this resource, {}", e5.getMessage());responseWrite(ResponseEntity.status(HttpStatus.FORBIDDEN).body("This account has no permission to access this resource"), servletResponse);return;} catch (RuntimeException e) {logger.error("other exception happen: ", e);responseWrite(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(),servletResponse);return;}try {// if ok, doFilter and add subject in requestfilterChain.doFilter(servletRequest, servletResponse);} finally {SurenessContextHolder.clear();}}/*** write response json data* @param content content* @param response response*/private void responseWrite(ResponseEntity<?> content, ServletResponse response) {response.setCharacterEncoding("UTF-8");response.setContentType("application/json;charset=utf-8");((HttpServletResponse)response).setStatus(content.getStatusCodeValue());content.getHeaders().forEach((key, value) ->((HttpServletResponse) response).addHeader(key, value.get(0)));try (PrintWriter printWriter = response.getWriter()) {if (content.getBody() != null) {if (content.getBody() instanceof String) {printWriter.write(content.getBody().toString());} else {ObjectMapper objectMapper = new ObjectMapper();printWriter.write(objectMapper.writeValueAsString(content.getBody()));}} else {printWriter.flush();}} catch (IOException e) {logger.error("responseWrite response error: ", e);}}
}

sureness.yml

## -- sureness.yml document dataSource-- ### load api resource which need be protected, config role who can access these resource.
# resources that are not configured are also authenticated and protected by default, but not authorized
# eg: /api/v2/host===post===[role2,role3,role4] means /api/v2/host===post can be access by role2,role3,role4
# eg: /api/v1/getSource3===get===[] means /api/v1/getSource3===get can not be access by any role
resourceRole:- /room/editRoomInfoById===post===[admin,teacher]- /room/getRoomList===get===[admin,teacher]- /user/addUserByExcel===post===[admin,teacher]- /user/changePassword===post===[student]- /user/changePasswordById===post===[admin,teacher]- /user/editUserInfo===post===[admin,teacher]- /user/getUserList/**===post===[admin,teacher]- /user/logout===post===[admin,teacher,student]- /appointment/cancelAppointmentById/**===delete===[admin,teacher,student]- /appointment/doAppointment===post===[admin,teacher,student]- /appointment/getAppointPage/**===post===[admin,teacher]# load api resource which do not need be protected, means them need be excluded.
# these api resource can be access by everyone
excludedResource:- /user/login===post- /room/getRoomInfoById/**===get- /user/getUserInfo===get- /user/getUserInfoById/*===get- /appointment/getHistory/**===get- /appointment/getRecent/**===get- /**/*.css===get- /**/*.ico===get- /**/*.png===get- /swagger-ui.html===*- /swagger/**===*- /swagger-resources/**===*- /v2/**===*- /webjars/**===*- /configuration/**===*
st===[role2,role3,role4] means /api/v2/host===post can be access by role2,role3,role4
# eg: /api/v1/getSource3===get===[] means /api/v1/getSource3===get can not be access by any role
resourceRole:- /room/editRoomInfoById===post===[admin,teacher]- /room/getRoomList===get===[admin,teacher]- /user/addUserByExcel===post===[admin,teacher]- /user/changePassword===post===[student]- /user/changePasswordById===post===[admin,teacher]- /user/editUserInfo===post===[admin,teacher]- /user/getUserList/**===post===[admin,teacher]- /user/logout===post===[admin,teacher,student]- /appointment/cancelAppointmentById/**===delete===[admin,teacher,student]- /appointment/doAppointment===post===[admin,teacher,student]- /appointment/getAppointPage/**===post===[admin,teacher]# load api resource which do not need be protected, means them need be excluded.
# these api resource can be access by everyone
excludedResource:- /user/login===post- /room/getRoomInfoById/**===get- /user/getUserInfo===get- /user/getUserInfoById/*===get- /appointment/getHistory/**===get- /appointment/getRecent/**===get- /**/*.css===get- /**/*.ico===get- /**/*.png===get- /swagger-ui.html===*- /swagger/**===*- /swagger-resources/**===*- /v2/**===*- /webjars/**===*- /configuration/**===*


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部