CXYVIP官网源码交易平台_网站源码_商城源码_小程序源码平台-丞旭猿论坛
CXYVIP官网源码交易平台_网站源码_商城源码_小程序源码平台-丞旭猿论坛
CXYVIP官网源码交易平台_网站源码_商城源码_小程序源码平台-丞旭猿论坛

​SpringBoot-零基础搭建前后端分离–后端搭建-永久免费的源码丞旭猿

SpringBoot-零基础搭建前后端分离–后端搭建

1.创建父项目verse

  1. 点击Create New Project

  1. 选择 Maven ,选择本地安装的JDK, 点击 Next

  1. 输入GroupID: com.verse 、ArtiactID:verse 点击 Finish

  1. 创建完成后,删除src

  1. pom.xml中添加依赖管理

<projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0modelVersion><groupId>com.versegroupId><artifactId>verseartifactId><version>1.0.0version><description>前后端分离versedescription><properties><java.version>1.8java.version><project.build.sourceEncoding>UTF-8project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding><maven.compiler.source>1.8maven.compiler.source><maven.compiler.target>1.8maven.compiler.target><spring.parent>2.5.13spring.parent><mybatis-plus-version>3.5.1mybatis-plus-version><hutool.all.version>5.5.7hutool.all.version><swagger.version>3.0.0swagger.version>properties><modules>modules><dependencyManagement><dependencies><dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-parentartifactId><version>${spring.parent}version><type>pomtype><scope>importscope>dependency><dependency><groupId>com.baomidougroupId><artifactId>mybatis-plus-boot-starterartifactId><version>${mybatis-plus-version}version>dependency><dependency><groupId>cn.hutoolgroupId><artifactId>hutool-allartifactId><version>${hutool.all.version}version>dependency><dependency><groupId>io.springfoxgroupId><artifactId>springfox-boot-starterartifactId><version>${swagger.version}version>dependency>dependencies>dependencyManagement><build><plugins><plugin><groupId>org.apache.maven.pluginsgroupId><artifactId>maven-compiler-pluginartifactId><configuration><source>${maven.compiler.source}source><target>${maven.compiler.target}target>configuration>plugin>plugins>build>project>

2.创建verse-commons

概述

通用异常处理以及通用响应数据结构等内容

新建verse-commons  Module

  1. 右击 verse模块名,点击 New > Module

  1. 选择 Maven ,选择本地安装的JDK, 点击 Next

  1. 输入GroupID:com.verse.commons、ArtiactID:verse-commons点击 Finish

  1. 修改verse-commons的pom.xml

<projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>verseartifactId><groupId>com.versegroupId><version>1.0.0version>parent><modelVersion>4.0.0modelVersion><groupId>com.verse.commonsgroupId><artifactId>verse-commonsartifactId><dependencies><dependency><groupId>org.projectlombokgroupId><artifactId>lombokartifactId><optional>trueoptional><scope>providedscope>dependency><dependency><groupId>com.fasterxml.jackson.coregroupId><artifactId>jackson-annotationsartifactId>dependency><dependency><groupId>org.springframeworkgroupId><artifactId>spring-webartifactId><scope>providedscope>dependency>dependencies>project>

添加统一返回结果

创建返回码接口IResultCode

/*** 返回码接口*/publicinterfaceIResultCode{/*** 返回码**@returnint*/intgetCode();/*** 返回消息**@returnString*/StringgetMsg();}

创建返回接口码的实现类ResultCode

packagecom.verse.commons.api;importlombok.AllArgsConstructor;importlombok.Getter;/*** 返回码实现*/@Getter@AllArgsConstructorpublicenumResultCode implements IResultCode {/*** 操作成功*/SUCCESS(200,"操作成功"),/*** 业务异常*/FAILURE(400,"业务异常"),/*** 业务异常*/Unauthorized(401,"用户、密码输入错误"),/*** 服务未找到*/NOT_FOUND(404,"服务未找到"),/*** 服务异常*/ERROR(500,"服务异常"),USER_INPUT_ERROR(400,"您输入的数据格式错误或您没有权限访问资源!"),/*** Too Many Requests*/TOO_MANY_REQUESTS(429,"Too Many Requests");/*** 状态码*/finalintcode;/*** 消息内容*/finalString msg;}

创建 统一响应消息报文Result类

/*** 统一响应消息报文*@param*/@Data@GetterpublicclassResult<T>implementsSerializable{privatestaticfinallongserialVersionUID =1L;privateintcode;privateString msg;privatelongtime;@JsonInclude(JsonInclude.Include.NON_NULL)privateT data;privateResult(){this.time = System.currentTimeMillis();}privateResult(IResultCode resultCode){this(resultCode,null, resultCode.getMsg());}privateResult(IResultCode resultCode, String msg){this(resultCode,null, msg);}privateResult(IResultCode resultCode, T data){this(resultCode, data, resultCode.getMsg());}privateResult(IResultCode resultCode, T data, String msg){this(resultCode.getCode(), data, msg);}privateResult(intcode, T data, String msg){this.code = code;this.data = data;this.msg = msg;this.time = System.currentTimeMillis();}/*** 返回状态码**@paramresultCode 状态码*@param        泛型标识*@returnApiResult*/publicstaticResultsuccess(IResultCode resultCode){returnnewResult<>(resultCode);}publicstaticResultsuccess(String msg){returnnewResult<>(ResultCode.SUCCESS, msg);}publicstaticResultsuccess(IResultCode resultCode, String msg){returnnewResult<>(resultCode, msg);}publicstaticResultdata(T data){returndata(data, VerseConstant.DEFAULT_SUCCESS_MESSAGE);}publicstaticResultdata(T data, String msg){returndata(ResultCode.SUCCESS.code, data, msg);}publicstaticResultdata(intcode, T data, String msg){returnnewResult<>(code, data, data ==null? VerseConstant.DEFAULT_NULL_MESSAGE : msg);}publicstaticResultfail(){returnnewResult<>(ResultCode.FAILURE, ResultCode.FAILURE.getMsg());}publicstaticResultfail(String msg){returnnewResult<>(ResultCode.FAILURE, msg);}publicstaticResultfail(intcode, String msg){returnnewResult<>(code,null, msg);}publicstaticResultfail(IResultCode resultCode){returnnewResult<>(resultCode);}publicstaticResultfail(IResultCode resultCode, String msg){returnnewResult<>(resultCode, msg);}publicstaticResultcondition(booleanflag){returnflag ? success(VerseConstant.DEFAULT_SUCCESS_MESSAGE) : fail(VerseConstant.DEFAULT_FAIL_MESSAGE);}}

创建基础异常处理类BaseException

packagecom.verse.commons.exception;importcom.verse.commons.api.ResultCode;importlombok.Data;importorg.springframework.http.HttpStatus;importstaticorg.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;/*** 基础异常处理类*/@DatapublicclassBaseExceptionextendsRuntimeException{privatestaticfinallongserialVersionUID =5782968730281544562L;privateintstatus = INTERNAL_SERVER_ERROR.value();publicBaseException(String message){super(message);}publicBaseException(HttpStatus status, String message){super(message);this.status = status.value();}publicBaseException(intcode, String message){super(message);this.code = code;this.message =message;}//异常错误编码privateintcode ;//异常信息privateString message;privateBaseException(){}publicBaseException(ResultCode resultCode){this.code = resultCode.getCode();this.message = resultCode.getMsg();}}

创建verse基本常量

/*** verse基本常量*/publicclassVerseConstant{/*** 默认成功消息*/publicstaticfinalString DEFAULT_SUCCESS_MESSAGE ="处理成功";/*** 默认失败消息*/publicstaticfinalString DEFAULT_FAIL_MESSAGE ="处理失败";/*** 默认为空消息*/publicstaticfinalString DEFAULT_NULL_MESSAGE ="承载数据为空";}

3.创建verse-jwt

概述

项目的后端核心服务(Spring Boot web应用)

新建verse-jwt   Module

  1. 右击 verse模块名,点击 New > Module

  1. 选择 Maven ,选择本地安装的JDK, 点击 Next

  1. 输入GroupID:com.verse.jwt、ArtiactID:verse-jwt点击 Finish

  1. 修改`verse-jwt的pom.xml

<projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>verseartifactId><groupId>com.versegroupId><version>1.0.0version>parent><modelVersion>4.0.0modelVersion><groupId>com.verse.jwtgroupId><artifactId>verse-jwtartifactId><dependencies><dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-webartifactId>dependency><dependency><groupId>com.verse.commonsgroupId><artifactId>verse-commonsartifactId><version>1.0.0version>dependency><dependency><groupId>mysqlgroupId><artifactId>mysql-connector-javaartifactId><scope>runtimescope>dependency><dependency><groupId>com.baomidougroupId><artifactId>mybatis-plus-boot-starterartifactId>dependency><dependency><groupId>com.baomidougroupId><artifactId>mybatis-plus-generatorartifactId>dependency><dependency><groupId>org.apache.velocitygroupId><artifactId>velocityartifactId>dependency><dependency><groupId>io.springfoxgroupId><artifactId>springfox-boot-starterartifactId>dependency>dependencies>project>

创建verse-jwt的入口类VerseJwtApplication

@MapperScan("com.verse.jwt.*.mapper")@SpringBootApplicationpublicclassVerseJwtApplication{publicstaticvoidmain(String[] args){SpringApplication.run(VerseJwtApplication.class,args);}}

4.在verse-jwt中实现代码生成

参考mybatis-plus代码生成:https://baomidou.com/pages/779a6e/

在verse-jwt中的pom.xml添加依赖

<dependency><groupId>com.baomidougroupId><artifactId>mybatis-plus-generatorartifactId>dependency>

创建MybatisPlusGenerator类

  • DATA_SOURCE_CONFIG:数据链接配置

  • generator:根据模板生成代码

  • getTables方法:数据库表,all表示库中所有表

  • 将模板添加到src\main\resources\templates文件下

publicclassMybatisPlusGenerator{privatestaticfinalDataSourceConfig.Builder DATA_SOURCE_CONFIG =newDataSourceConfig.Builder("jdbc:mysql://localhost:3306/verse?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai","root","root");publicstaticvoidgenerator(){FastAutoGenerator.create(DATA_SOURCE_CONFIG)// 全局配置.globalConfig(builder -> {builder.author("springboot葵花宝典")// 设置作者.enableSwagger()// 开启 swagger 模式.fileOverride()// 覆盖已生成文件.outputDir("D:\\software\\file\\verse");// 指定输出目录})// 包配置.packageConfig((scanner, builder) -> builder.parent(scanner.apply("请输入包名?")).pathInfo(Collections.singletonMap(OutputFile.xml,"D:\\software\\file\\verse\\mapper")))// 策略配置.strategyConfig((scanner, builder) -> builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all"))).controllerBuilder().enableRestStyle().enableHyphenStyle().entityBuilder().enableLombok()//                        .addTableFills(//                                new Column("create_by", FieldFill.INSERT),//                                new Column("create_time", FieldFill.INSERT),//                                new Column("update_by", FieldFill.INSERT_UPDATE),//                                new Column("update_time", FieldFill.INSERT_UPDATE)//                        ).build())/*模板引擎配置,默认 Velocity 可选模板引擎 Beetl 或 Freemarker.templateEngine(new BeetlTemplateEngine()).templateEngine(new FreemarkerTemplateEngine())*/.execute();}// 处理 all 情况protectedstaticListgetTables(String tables){return"all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));}}

创建代码生成入口类GeneratorMain

publicclassGeneratorMain{publicstaticvoidmain(String[] args){MybatisPlusGenerator.generator();}}

运行GeneratorMain

生成结果如下

将system包放在verse-jwt的com.verse.jwt包下,结果如下:

将mapper放在verse-jwtsrc\main\resources包下,结果如下:

5.整合Swagger-ui实现在线API文档

添加项目依赖

verse-jwt项目的pom.xml中新增Swagger-UI相关依赖

<dependency><groupId>io.springfoxgroupId><artifactId>springfox-boot-starterartifactId>dependency>

添加Swagger-UI的配置

verse-jwt项目中添加如下类Swagger2Config:

packagecom.verse.jwt.config;importorg.springframework.beans.BeansException;importorg.springframework.beans.factory.config.BeanPostProcessor;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.util.ReflectionUtils;importorg.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;importspringfox.documentation.builders.ApiInfoBuilder;importspringfox.documentation.builders.PathSelectors;importspringfox.documentation.builders.RequestHandlerSelectors;importspringfox.documentation.service.*;importspringfox.documentation.spi.DocumentationType;importspringfox.documentation.spi.service.contexts.SecurityContext;importspringfox.documentation.spring.web.plugins.Docket;importspringfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;importspringfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;importjava.lang.reflect.Field;importjava.util.ArrayList;importjava.util.List;importjava.util.stream.Collectors;@ConfigurationpublicclassSwagger2Config{@BeanpublicDocketcreateRestApi(){returnnewDocket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.basePackage("com.verse.jwt.system.controller")).paths(PathSelectors.any()).build();}privateApiInfoapiInfo(){returnnewApiInfoBuilder().title("SwaggerUI演示").description("verse").contact(newContact("springboot葵花宝典",null,null)).version("1.0").build();}@BeanpublicstaticBeanPostProcessorspringfoxHandlerProviderBeanPostProcessor(){returnnewBeanPostProcessor() {@OverridepublicObjectpostProcessAfterInitialization(Object bean, String beanName)throwsBeansException{if(beaninstanceofWebMvcRequestHandlerProvider || beaninstanceofWebFluxRequestHandlerProvider) {customizeSpringfoxHandlerMappings(getHandlerMappings(bean));}returnbean;}privatevoidcustomizeSpringfoxHandlerMappings(List mappings){List copy = mappings.stream().filter(mapping -> mapping.getPatternParser() ==null).collect(Collectors.toList());mappings.clear();mappings.addAll(copy);}@SuppressWarnings("unchecked")privateListgetHandlerMappings(Object bean){try{Field field = ReflectionUtils.findField(bean.getClass(),"handlerMappings");field.setAccessible(true);return(List) field.get(bean);}catch(IllegalArgumentException | IllegalAccessException e) {thrownewIllegalStateException(e);}}};}}

修改配置

修改application.yml文件,MVC默认的路径匹配策略为PATH_PATTERN_PARSER,需要修改为ANT_PATH_MATCHER

spring:jackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT+8datasource:url: jdbc:mysql://localhost:3306/verse?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Drivermybatis-plus:config-location:mapper-locations:- classpath:mapper/*.xml- classpath*:com/**/mapper/*.xmlconfiguration:map-underscore-to-camel-case:truespringfox:documentation:enabled:trueserver:port: 8888

添加一个测试接口

  • 在ISysUserService接口中添加方法

importcom.verse.jwt.system.entity.SysUser;importcom.baomidou.mybatisplus.extension.service.IService;/*** 

* 用户信息表 服务类* 

**@authorspringboot葵花宝典*@since2022-04-27*/
publicinterfaceISysUserServiceextendsIService<SysUser>{SysUsergetUserByUserName(String userName);}
  • 在SysUserServiceImpl中实现

importcn.hutool.core.util.StrUtil;importcom.baomidou.mybatisplus.core.conditions.query.QueryWrapper;importcom.verse.jwt.system.entity.SysUser;importcom.verse.jwt.system.mapper.SysUserMapper;importcom.verse.jwt.system.service.ISysUserService;importcom.baomidou.mybatisplus.extension.service.impl.ServiceImpl;importorg.springframework.stereotype.Service;importorg.springframework.util.Assert;importjavax.annotation.Resource;/*** 

* 用户信息表 服务实现类* 

**@authorspringboot葵花宝典*@since2022-04-27*/
@ServicepublicclassSysUserServiceImplextendsServiceImpl<SysUserMapper,SysUser>implementsISysUserService{@ResourceprivateSysUserMapper sysUserMapper;/*** 根据登录用户名查询用户信息*@paramuserName 用户信息*@return*/@OverridepublicSysUsergetUserByUserName(String userName){Assert.isTrue(StrUtil.isNotEmpty(userName),"查询参数用户名不存在");SysUser sysUser = sysUserMapper.selectOne(newQueryWrapper().eq("username",userName));if(sysUser !=null){sysUser.setPassword("");//清空密码信息}returnsysUser;}}
  • 在SysUserController中实现接口

importcom.verse.jwt.system.entity.SysUser;importcom.verse.jwt.system.service.ISysUserService;importio.swagger.annotations.ApiOperation;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestMapping;importio.swagger.annotations.Api;importorg.springframework.web.bind.annotation.RequestParam;importorg.springframework.web.bind.annotation.RestController;importjavax.annotation.Resource;/*** 

* 用户信息表 前端控制器* 

**@authorspringboot葵花宝典*@since2022-04-27*/
@RestController@Api(tags ="SysUserController", description =" 用户信息表")@RequestMapping("/sys-user")publicclassSysUserController{@ResourceprivateISysUserService sysuserService;/*** 根据登录用户名查询用户信息*@paramusername 用户名称*@return*/@ApiOperation(value ="info")@GetMapping(value ="/info")publicSysUserinfo(@RequestParam("username")String username){returnsysuserService.getUserByUserName(username);}}

测试

启动项目后,访问http://localhost:8888/swagger-ui/地址,结果如下

6.整合SpringSecurity和JWT实现认证和授权

项目使用表说明

  • sys_user是用户信息表,用于存储用户的基本信息,如:用户名、密码

  • sys_role是角色信息表,用于存储系统内所有的角色

  • sys_menu是系统的菜单信息表,用于存储系统内所有的菜单。用id与父id的字段关系维护一个菜单树形结构。

  • sys_user_role是用户角色多对多关系表,一条userid与roleid的关系记录表示该用户具有该角色,该角色包含该用户。

  • sys_role_menu是角色菜单(权限)关系表,一条roleid与menuid的关系记录表示该角色由某菜单权限,该菜单权限可以被某角色访问。

  • sys_api,用于存储可以被访问的资源服务接口

  • sys_role_api,一条roleid与apiid的关系记录表示该角色具有某个api接口的访问权限。

添加项目依赖

  • verse-jtw的pom.xml中添加security项目依赖

<dependency><groupId>org.springframework.bootgroupId><artifactId>spring-boot-starter-securityartifactId>dependency><dependency><groupId>io.jsonwebtokengroupId><artifactId>jjwtartifactId>dependency>

添加Jwt属性配置类

  1. JwtProperties属性类

/*** jwt配置的属性*/@Data@Component@ConfigurationProperties("verse.jwt")publicclassJwtProperties{//是否开启JWT,即注入相关的类对象privateBoolean enabled;//JWT密钥privateString secret;//JWT有效时间privateLong expiration;//前端向后端传递JWT时使用HTTP的header名称privateString header;//用户获取JWT令牌发送的用户名参数名称privateString userParamName ="username";//用户获取JWT令牌发送的密码参数名称privateString pwdParamName ="password";//允许哪些域对本服务的跨域请求privateList corsAllowedOrigins;//允许哪些HTTP方法跨域privateList corsAllowedMethods;//是否关闭csrf跨站攻击防御功能privateBoolean csrfDisabled =true;//是否使用默认的JWTAuthControllerprivateBoolean useDefaultController =true;}
  1. VerseApiProperties属性类

VerseApiProperties权限全面开放的接口,不需要JWT令牌就可以访问,或者开发过程临时开放的URI

/*** 权限全面开放的接口,不需要JWT令牌就可以访问,或者开发过程临时开放的URI*/@Data@Component@ConfigurationProperties(prefix ="verse.uaa")publicclassVerseApiProperties{/*** 监控中心和swagger需要访问的url*/publicstaticfinalString[] ENDPOINTS = {"/jwtauth/**","/swagger-ui/swagger-resources/**","/swagger-resources/**","/webjars/**","/swagger-ui/**","/v2/api-docs","/v3/api-docs",};/*** 忽略URL,List列表形式*/privateList ignoreUrl =newArrayList<>();/*** 首次加载合并ENDPOINTS*/@PostConstructpublicvoidinitIgnoreUrl(){Collections.addAll(ignoreUrl, ENDPOINTS);}}
  1. application.yml添加注解

verse:jwt:enabled:truesecret:verseexpiration:3600000header:JWTHeaderNameuserParamName:usernamepwdParamName:passwordcorsAllowedOrigins:-http://localhost:8080-http://127.0.0.1:8080corsAllowedMethods:-GET-POSTuseDefaultController:trueuaa:ignoreUrl:权限全面开放的接口,不需要JWT令牌就可以访问,或者开发过程临时开放的URI-/sys-user/info根据用户名获取用户信息

添加JWT token的工具类

  1. 添加JWT token的工具类用于生成和解析JWT token的工具类

相关方法说明:

  • String generateToken(String username,Map payloads) :用于根据登录用户信息生成token

  • String getUsernameFromToken(String token):从token中获取登录用户的信息

  • Boolean validateToken(String token, String usernameParam):判断token是否还有效

/*** JwtToken生成工具类*/@Slf4j@ComponentpublicclassJwtTokenUtil{@AutowiredprivateJwtProperties jwtProperties;privatestaticfinalString CLAIM_KEY_CREATED ="created";/*** 根据用户信息生成JWT的token令牌**@paramusername 用户*@parampayloads 令牌中携带的附加信息*@return令token牌*/publicStringgenerateToken(String username,Map payloads){intpayloadSizes = payloads ==null?0: payloads.size();Map claims =newHashMap<>(payloadSizes +2);claims.put("sub", username);claims.put("created",newDate());if(payloadSizes >0){for(Map.Entry entry:payloads.entrySet()){claims.put(entry.getKey(),entry.getValue());}}returngenerateToken(claims);}/*** 从token中获取JWT中的负载**@paramtoken 令牌*@return数据声明*/privateClaimsgetClaimsFromToken(String token){Claims claims;try{claims = Jwts.parser().setSigningKey(jwtProperties.getSecret()).parseClaimsJws(token).getBody();}catch(Exception e) {claims =null;}returnclaims;}/*** 生成token的过期时间*/privateDategenerateExpirationDate(){returnnewDate(System.currentTimeMillis() + + jwtProperties.getExpiration());}/*** 从token令牌中获取登录用户名**@paramtoken 令牌*@return用户名*/publicStringgetUsernameFromToken(String token){String username;try{Claims claims = getClaimsFromToken(token);username = claims.getSubject();}catch(Exception e) {username =null;}returnusername;}/*** 验证token是否还有效**@paramtoken  客户端传入的token令牌*@paramusernameParam 用户名的唯一标识*@return是否有效*/publicBooleanvalidateToken(String token, String usernameParam){//根据toekn获取用户名String username = getUsernameFromToken(token);return(username.equals(usernameParam) && !isTokenExpired(token));}/*** 判断令牌是否过期**@paramtoken 令牌*@return是否过期*/publicBooleanisTokenExpired(String token){try{//根据token获取获取过期时间Date expiration = getExpiredDateFromToken( token);returnexpiration.before(newDate());}catch(Exception e) {returnfalse;}}/*** 从token中获取过期时间*/privateDategetExpiredDateFromToken(String token){Claims claims = getClaimsFromToken(token);returnclaims.getExpiration();}/*** 根据负责生成JWT的token**@paramclaims 数据声明*@return令牌*/privateStringgenerateToken(Map claims){//生成token的过期时间Date expirationDate = generateExpirationDate();returnJwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, jwtProperties.getSecret()).compact();}/*** 刷新令牌**@paramtoken 原令牌*@return新令牌*/publicStringrefreshToken(String token){String refreshedToken;try{//获取负载信息Claims claims = getClaimsFromToken(token);claims.put(CLAIM_KEY_CREATED,newDate());refreshedToken = generateToken(claims);}catch(Exception e) {refreshedToken =null;}returnrefreshedToken;}/*** 从token令牌中获取登录用户名**@return用户名*/publicStringgetUsernameFromToken(){String username;try{RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();String jwtToken = request.getHeader(jwtProperties.getHeader());Claims claims = getClaimsFromToken(jwtToken);username = claims.getSubject();}catch(Exception e) {username =null;}returnusername;}}

添加SpringSecurity的配置类

/*** Spring Security 配置* 可以配置多个WebSecurityConfigurerAdapter* 但是多个Adaptor有执行顺序,默认值是100* 这里设置为1会优先执行*/@Configuration@Order(1)publicclassJwtSecurityConfigurerAdapterextendsWebSecurityConfigurerAdapter{@ResourceprivateJwtProperties jwtProperties;@ResourceprivateVerseApiProperties apiProperties;@ResourceprivateMyUserDetailsService myUserDetailsService;@ResourceprivateJwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@ResourceprivateRestfulAccessDeniedHandler restfulAccessDeniedHandler;@ResourceprivateRestAuthenticationEntryPoint restAuthenticationEntryPoint;@Overridepublicvoidconfigure(HttpSecurity http)throwsException{if(jwtProperties.getCsrfDisabled()){http = http.csrf().disable();}http.cors().and().sessionManagement()// 基于token,所以不需要session.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class).authorizeRequests();//通过配置实现的不需要JWT令牌就可以访问的接口for(String uri : apiProperties.getIgnoreUrl()){http.authorizeRequests().antMatchers(uri).permitAll();}//RBAC权限控制级别的接口权限校验http.authorizeRequests().anyRequest().authenticated()//.access("@rabcService.hasPermission(request,authentication)");//添加自定义未授权和未登录结果返回http.exceptionHandling().accessDeniedHandler(restfulAccessDeniedHandler).authenticationEntryPoint(restAuthenticationEntryPoint);}@Overridepublicvoidconfigure(AuthenticationManagerBuilder auth)throwsException{auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder());}@Overridepublicvoidconfigure(WebSecurity web){//将项目中静态资源路径开放出来web.ignoring().antMatchers(apiProperties.ENDPOINTS);}@BeanpublicPasswordEncoderpasswordEncoder(){returnnewBCryptPasswordEncoder();}/*** 跨站资源共享配置*/@BeanCorsConfigurationSourcecorsConfigurationSource(){CorsConfiguration configuration =newCorsConfiguration();configuration.setAllowedOrigins(jwtProperties.getCorsAllowedOrigins());configuration.setAllowedMethods(jwtProperties.getCorsAllowedMethods());configuration.applyPermitDefaultValues();UrlBasedCorsConfigurationSource source =newUrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", configuration);returnsource;}@Override@Bean(name = BeanIds.AUTHENTICATION_MANAGER)publicAuthenticationManagerauthenticationManagerBean()throwsException{returnsuper.authenticationManagerBean();}@BeanpublicJwtAuthServicejwtAuthService(JwtTokenUtil jwtTokenUtil)throwsException{returnnewJwtAuthService(this.authenticationManagerBean(),jwtTokenUtil);}}

相关依赖及方法说明

  • corsConfigurationSource: 跨域设置

  • configure(HttpSecurity httpSecurity):用于配置需要拦截的url路径、jwt过滤器及出异常后的处理器;

  • configure(AuthenticationManagerBuilder auth):用于配置UserDetailsService及PasswordEncoder;

  • RestfulAccessDeniedHandler:当用户没有访问权限时的处理器,用于返回JSON格式的处理结果;

  • RestAuthenticationEntryPoint:当未登录或token失效时,返回JSON格式的结果;

  • UserDetailsService:SpringSecurity定义的核心接口,用于根据用户名获取用户信息,需要自行实现;

  • UserDetails:SpringSecurity定义用于封装用户信息的类(主要是用户信息和权限),需要自行实现;

  • PasswordEncoder:SpringSecurity定义的用于对密码进行编码及比对的接口,目前使用的是BCryptPasswordEncoder;

  • JwtAuthenticationTokenFilter:在用户名和密码校验前添加的过滤器,如果有jwt的token,会自行根据token信息进行登录

  • JwtAuthService:认证服务Service

添加RestfulAccessDeniedHandler

importcn.hutool.json.JSONUtil;importcom.verse.commons.api.Result;importorg.springframework.security.access.AccessDeniedException;importorg.springframework.security.web.access.AccessDeniedHandler;importorg.springframework.stereotype.Component;importjavax.servlet.ServletException;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;/*** 当访问接口没有权限时,自定义的返回结果*/@ComponentpublicclassRestfulAccessDeniedHandlerimplementsAccessDeniedHandler{@Overridepublicvoidhandle(HttpServletRequest request,HttpServletResponse response,AccessDeniedException e)throwsIOException, ServletException{response.setCharacterEncoding("UTF-8");response.setContentType("application/json");response.getWriter().println(JSONUtil.parse(Result.fail(e.getMessage())));response.getWriter().flush();}}

添加RestAuthenticationEntryPoint

importcn.hutool.json.JSONUtil;importcom.verse.commons.api.Result;importorg.springframework.security.core.AuthenticationException;importorg.springframework.security.web.AuthenticationEntryPoint;importorg.springframework.stereotype.Component;importjavax.servlet.ServletException;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;/*** 当未登录或者token失效访问接口时,自定义的返回结果*/@ComponentpublicclassRestAuthenticationEntryPointimplementsAuthenticationEntryPoint{@Overridepublicvoidcommence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)throwsIOException, ServletException{response.setCharacterEncoding("UTF-8");response.setContentType("application/json");response.getWriter().println(JSONUtil.parse(Result.fail(authException.getMessage())));response.getWriter().flush();}}

添加MyUserDetailsServiceMapper

importcom.verse.jwt.auth.dto.MyUserDetails;importorg.apache.ibatis.annotations.Param;importorg.apache.ibatis.annotations.Select;importjava.util.List;/*** 用户信息Mapper*/publicinterfaceMyUserDetailsServiceMapper{//根据username查询用户信息@Select("SELECT username,password,enabled\n"+"FROM sys_user u\n"+"WHERE u.username = {username}")MyUserDetailsfindByUserName(@Param("username")String username);//根据username查询用户角色列表@Select("SELECT role_code\n"+"FROM sys_role r\n"+"LEFT JOIN sys_user_role ur ON r.id = ur.role_id  AND r.status = 0\n"+"LEFT JOIN sys_user u ON u.id = ur.user_id\n"+"WHERE u.username = {username}")ListfindRoleByUserName(@Param("username")String username);//根据用户角色查询用户菜单权限@Select({""})ListfindMenuByRoleCodes(@Param("roleCodes")List roleCodes);//根据用户角色查询用户接口访问权限@Select("SELECT url \n"+"FROM sys_api a \n"+"LEFT JOIN sys_role_api ra ON a.id = ra.api_id \n"+"LEFT JOIN sys_role r ON r.id = ra.role_id \n"+"WHERE r.role_code = {roleCode} \n"+"AND a.status = 0")ListfindApiByRoleCode(@Param("roleCode")String roleCode);}

添加MyUserDetails

publicclassMyUserDetailsimplementsUserDetails{String password;//密码String username;//用户名booleanaccountNonExpired;//是否没过期booleanaccountNonLocked;//是否没被锁定booleancredentialsNonExpired;//是否没过期booleanenabled;//账号是否可用Collection authorities;//用户的权限集合@OverridepublicCollection getAuthorities() {returnauthorities;}@OverridepublicStringgetPassword(){returnpassword;}@OverridepublicStringgetUsername(){returnusername;}@OverridepublicbooleanisAccountNonExpired(){returntrue;}@OverridepublicbooleanisAccountNonLocked(){returntrue;}@OverridepublicbooleanisCredentialsNonExpired(){returntrue;}@OverridepublicbooleanisEnabled(){returnenabled;}publicvoidsetPassword(String password){this.password = password;}publicvoidsetUsername(String username){this.username = username;}publicvoidsetAccountNonExpired(booleanaccountNonExpired){this.accountNonExpired = accountNonExpired;}publicvoidsetAccountNonLocked(booleanaccountNonLocked){this.accountNonLocked = accountNonLocked;}publicvoidsetCredentialsNonExpired(booleancredentialsNonExpired){this.credentialsNonExpired = credentialsNonExpired;}publicvoidsetEnabled(booleanenabled){this.enabled = enabled;}publicvoidsetAuthorities(Collection authorities){this.authorities = authorities;}}

添加JwtAuthService

JwtAuthService是认证服务的Service

/*** 认证登录服务*/publicclassJwtAuthService{privateAuthenticationManager authenticationManager;privateJwtTokenUtil jwtTokenUtil;@ResourceprivateMyUserDetailsServiceMapper myUserDetailsServiceMapper;privateJwtAuthService(){}publicJwtAuthService(AuthenticationManager authenticationManager,JwtTokenUtil jwtTokenUtil){this.authenticationManager = authenticationManager;this.jwtTokenUtil = jwtTokenUtil;}/*** 登录认证换取JWT令牌*@returnJWT*/publicStringlogin(String username,String password,Map payloads)throwsBaseException{try{UsernamePasswordAuthenticationToken upToken =newUsernamePasswordAuthenticationToken(username, password);Authentication authentication = authenticationManager.authenticate(upToken);SecurityContextHolder.getContext().setAuthentication(authentication);}catch(AuthenticationException e){thrownewBaseException(ResultCode.FAILURE.getCode(),"用户名或者密码输入错误,或者新建用户未赋予角色权限!");}returnjwtTokenUtil.generateToken(username,payloads);}publicStringrefreshToken(String oldToken){if(!jwtTokenUtil.isTokenExpired(oldToken)){returnjwtTokenUtil.refreshToken(oldToken);}returnnull;}/*** 获取角色信息列表*@paramtoken*@return*/publicListroles(String token){String username = jwtTokenUtil.getUsernameFromToken(token);//加载用户角色列表List roleCodes =myUserDetailsServiceMapper.findRoleByUserName(username);returnroleCodes;}}

添加MyRBACService

MyRBACService用户角色service

/*** 权限服务*/@Component("verseService")publicclassMyRBACService{privateAntPathMatcher antPathMatcher =newAntPathMatcher();@ResourceprivateVerseApiProperties verseApiProperties;@ResourceprivateMyUserDetailsServiceMapper myUserDetailsServiceMapper;/*** 判断某用户是否具有该request资源的访问权限*/publicbooleanhasPermission(HttpServletRequest request, Authentication authentication){Object principal = authentication.getPrincipal();if(principalinstanceofUserDetails){UserDetails userDetails = ((UserDetails)principal);List authorityList =AuthorityUtils.commaSeparatedStringToAuthorityList(request.getRequestURI());returnuserDetails.getAuthorities().contains(authorityList.get(0))|| verseApiProperties.getIgnoreUrl().contains(request.getRequestURI());}returnfalse;}publicMyUserDetailsfindByUserName(String username){returnmyUserDetailsServiceMapper.findByUserName(username);}publicListfindRoleByUserName(String username){returnmyUserDetailsServiceMapper.findRoleByUserName(username);}publicListfindApiByRoleCode(String roleCode){returnmyUserDetailsServiceMapper.findApiByRoleCode(roleCode);}}

添加MyUserDetailsService

@ComponentpublicclassMyUserDetailsServiceimplementsUserDetailsService{@ResourceMyRBACService myRBACService;@OverridepublicUserDetailsloadUserByUsername(String username)throwsUsernameNotFoundException{//加载基础用户信息MyUserDetails myUserDetails = myRBACService.findByUserName(username);//加载用户角色列表List roleCodes = myRBACService.findRoleByUserName(username);List authorities =newArrayList<>();for(String roleCode : roleCodes){//通过用户角色列表加载用户的资源权限列表authorities.addAll(myRBACService.findApiByRoleCode(roleCode));}//角色是一个特殊的权限,ROLE_前缀roleCodes = roleCodes.stream().map(rc ->"ROLE_"+rc).collect(Collectors.toList());authorities.addAll(roleCodes);myUserDetails.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",",authorities)));returnmyUserDetails;}}

添加JwtAuthenticationTokenFilter

在用户名和密码校验前添加的过滤器,如果请求中有jwt的token且有效,会取出token中的用户名,然后调用SpringSecurity的API进行登录操作。

/*** JWT令牌授权过滤器* 1.判断令牌的有效性* 2.根据令牌为该用户授权可以访问的资源*/@Slf4j@Configurationpublic class JwtAuthenticationTokenFilter extends OncePerRequestFilter {private JwtProperties jwtProperties;private JwtTokenUtil jwtTokenUtil;private MyUserDetailsService myUserDetailsService;privateJwtAuthenticationTokenFilter(){}public JwtAuthenticationTokenFilter(JwtProperties jwtProperties,JwtTokenUtil jwtTokenUtil,MyUserDetailsService myUserDetailsService) {this.jwtProperties = jwtProperties;this.jwtTokenUtil = jwtTokenUtil;this.myUserDetailsService = myUserDetailsService;}@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain)throws ServletException, IOException {//获取tokenString authHeader = request.getHeader(jwtProperties.getHeader());if(!StrUtil.isEmpty(authHeader) ) {String username = jwtTokenUtil.getUsernameFromToken(authHeader);logger.info("checking username:"+ username);if(username != null && SecurityContextHolder.getContext().getAuthentication() == null) {UserDetails userDetails = this.myUserDetailsService.loadUserByUsername(username);if(jwtTokenUtil.validateToken(authHeader, username)) {//给使用该JWT令牌的用户进行授权UsernamePasswordAuthenticationToken authentication =new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());logger.info("authenticated user:"+username);SecurityContextHolder.getContext().setAuthentication(authentication);}}}filterChain.doFilter(request, response);}}

添加JwtAuthController

JwtAuthController主要用户获取token,刷新token

@RestController@Api(tags ="JwtAuthController")@RequestMapping("/jwtauth")publicclassJwtAuthController{@ResourceprivateJwtProperties jwtProperties;@ResourceprivateJwtAuthService jwtAuthService;@ResourceprivateMyUserDetailsServiceMapper myUserDetailsServiceMapper;/*** 使用用户名密码换JWT令牌*/@ApiOperation(value = JWTConstants.CONTROLLER_AUTHENTICATION)@RequestMapping(value = JWTConstants.CONTROLLER_AUTHENTICATION)publicResultlogin(@RequestBody Map map){String username  = map.get(jwtProperties.getUserParamName());String password = map.get(jwtProperties.getPwdParamName());if(StrUtil.isEmpty(username)|| StrUtil.isEmpty(password)){returnResult.fail(ResultCode.Unauthorized,"用户名或者密码不能为空");}try{returnResult.data(jwtAuthService.login(username, password,null));}catch(BaseException e){returnResult.fail(ResultCode.FAILURE,e.getMessage());}}/*** 刷新JWT令牌*/@ApiOperation(value = JWTConstants.CONTROLLER_REFRESH)@RequestMapping(value = JWTConstants.CONTROLLER_REFRESH)publicResultrefresh(@RequestHeader("${verse.jwt.header}")String token){returnResult.data(jwtAuthService.refreshToken(token));}/*** 获取用户角色列表接口*/@ApiOperation(value = JWTConstants.CONTROLLER_ROLES)@RequestMapping(value = JWTConstants.CONTROLLER_ROLES)publicResultroles(@RequestHeader("${verse.jwt.header}")String token){returnResult.data(jwtAuthService.roles(token));}}

修改Swagger的配置

通过修改配置实现调用接口自带Authorization头,这样就可以访问需要登录的接口了。

packagecom.verse.jwt.config;importio.swagger.annotations.Api;importorg.springframework.beans.BeansException;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.beans.factory.config.BeanPostProcessor;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.util.ReflectionUtils;importorg.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;importspringfox.documentation.builders.ApiInfoBuilder;importspringfox.documentation.builders.PathSelectors;importspringfox.documentation.builders.RequestHandlerSelectors;importspringfox.documentation.service.*;importspringfox.documentation.spi.DocumentationType;importspringfox.documentation.spi.service.contexts.SecurityContext;importspringfox.documentation.spring.web.plugins.Docket;importspringfox.documentation.spring.web.plugins.WebFluxRequestHandlerProvider;importspringfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;importjava.lang.reflect.Field;importjava.util.ArrayList;importjava.util.List;importjava.util.stream.Collectors;@ConfigurationpublicclassSwagger2Config{@Value("${verse.jwt.header}")privateString header ;@BeanpublicDocketcreateRestApi(){returnnewDocket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()//.apis(RequestHandlerSelectors.basePackage("com.verse.jwt.system.controller"))//为有@Api注解的Controller生成API文档.apis(RequestHandlerSelectors.withClassAnnotation(Api.class)).paths(PathSelectors.any()).build()//添加登录认证.securitySchemes(securitySchemes()).securityContexts(securityContexts());}privateListsecuritySchemes(){//设置请求头信息List result =newArrayList<>();ApiKey apiKey =newApiKey("JWTVerseHeaderName","JWTVerseHeaderName","header");result.add(apiKey);returnresult;}privateListsecurityContexts(){//设置需要登录认证的路径List result =newArrayList<>();result.add(getContextByPath("/sys-role/.*"));returnresult;}privateSecurityContextgetContextByPath(String pathRegex){returnSecurityContext.builder().securityReferences(defaultAuth()).forPaths(PathSelectors.regex(pathRegex)).build();}privateListdefaultAuth(){List result =newArrayList<>();AuthorizationScope authorizationScope =newAuthorizationScope("global","accessEverything");AuthorizationScope[] authorizationScopes =newAuthorizationScope[1];authorizationScopes[0] = authorizationScope;result.add(newSecurityReference("JWTVerseHeaderName", authorizationScopes));returnresult;}privateApiInfoapiInfo(){returnnewApiInfoBuilder().title("SwaggerUI演示").description("verse").contact(newContact("springboot葵花宝典",null,null)).version("1.0").build();}@BeanpublicstaticBeanPostProcessorspringfoxHandlerProviderBeanPostProcessor(){returnnewBeanPostProcessor() {@OverridepublicObjectpostProcessAfterInitialization(Object bean, String beanName)throwsBeansException{if(beaninstanceofWebMvcRequestHandlerProvider || beaninstanceofWebFluxRequestHandlerProvider) {customizeSpringfoxHandlerMappings(getHandlerMappings(bean));}returnbean;}privatevoidcustomizeSpringfoxHandlerMappings(List mappings){List copy = mappings.stream().filter(mapping -> mapping.getPatternParser() ==null).collect(Collectors.toList());mappings.clear();mappings.addAll(copy);}@SuppressWarnings("unchecked")privateListgetHandlerMappings(Object bean){try{Field field = ReflectionUtils.findField(bean.getClass(),"handlerMappings");field.setAccessible(true);return(List) field.get(bean);}catch(IllegalArgumentException | IllegalAccessException e) {thrownewIllegalStateException(e);}}};}}

认证与授权流程演示

运行项目,访问API

Swagger api地址:http://localhost:8888/swagger-ui/

免密登录访问接口

未登录前访问接口

获取token

  • 点击Authorize按钮,在弹框中输入登录接口中获取到的token信息

  • 登录后访问获取权限列表接口,发现已经可以正常访问

如果您觉得本文不错,欢迎关注,点赞,收藏支持,您的关注是我坚持的动力!

原创不易,转载请注明出处,感谢支持!如果本文对您有用,欢迎转发分享!

声明:本文部分素材转载自互联网,如有侵权立即删除 。

© 版权声明
THE END
喜欢就支持一下吧
点赞0赞赏 分享
相关推荐
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容