Vue+SpringCloudAlibaba+JWT+Spring-security实现简单登录和CURD
父模块的依赖
<properties>
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.7.RELEASE</spring-cloud-alibaba.version>
<java.version>1.8</java.version>
<mybatis-version>3.4.0</mybatis-version>
<druid-version>1.1.10</druid-version>
<mysql-version>8.0.28</mysql-version>
<jwt-version>3.8.0</jwt-version>
<swagger-version>2.9.2</swagger-version>
<test-version>2.6.7</test-version>
<web-version>2.6.7</web-version>
<lobomk-version>1.18.24</lobomk-version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-version}</version>
</dependency>
<!--druid 数据源 阿里巴巴提供的一款性能非常高的数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${web-version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lobomk-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${test-version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger-version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger-version}</version>
</dependency>
<!--jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${jwt-version}</version>
</dependency>
</dependencies>
</dependencyManagement>
yuan-utils工具模块
JWTUtil
/**
* @author mo
* @Description: Jwt工具类,用于生成和校验jwt
*/
public class JWTUtil {
/**过期时间为半小时*/
private static final long EXPRIE_TIME = 30*60*1000;//30分钟
/**加密时的密钥,不能泄露*/
private static final String secret = "B8B7EDFB-53AC-2BD2-E804-461A0C961F29";
/**
* 校验用户名和密码
* @param token
* @param username
* @return
*/
public static boolean verify(String token) {
try {
// 加密方式必须与生成token时的加密方式一致
Algorithm algorithm = Algorithm.HMAC512(secret);
JWTVerifier verifier = JWT.require(algorithm)
.build();
verifier.verify(token);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 获取token中的信息无需secret解密也能获得
* @param token
* @return
*/
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 获取token中的信息无需secret解密也能获得
* @param token
* @return
*/
public static String getUserId(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("userId").asString();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 生成JWTToken自定义 ItripUserDetails 用于封装用户信息
* @param username
* @return
* @throws UnsupportedEncodingException
*/
public static String sign(String username,String userId) throws
UnsupportedEncodingException {
Date date = new Date(System.currentTimeMillis()+EXPRIE_TIME);
Algorithm algorithm = Algorithm.HMAC512(secret);
// 附带username信息
return JWT.create()
.withClaim("username", username)
.withClaim("userId", userId)
.withExpiresAt(date)
.sign(algorithm);
}
}
yuan-beans公共模块
依赖
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
</dependency>
</dependencies>
dto包
package com.yuan.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class R {
@ApiModelProperty(value = "是否成功标示,true:成功;false:失败")
private String success;
@ApiModelProperty(value = "异常码")
private String errorCode;
@ApiModelProperty(value = "响应提示信息")
private String msg;
@ApiModelProperty(value = "返回数据")
private Object data;
//把构造方法私有
private R(){}
//成功静态方法
public static R ok(){
R r = new R();
r.setSuccess("true");
r.setErrorCode(ResultCode.SUCCESS);
r.setMsg("成功");
return r;
}
//失败静态方法
public static R error(){
R r = new R();
r.setSuccess("false");
r.setErrorCode(ResultCode.ERROR);
r.setMsg("失败");
return r;
}
public R success(String success){
this.setSuccess(success);
return this;
}
public R message(String message){
this.setMsg(message);
return this;
}
public R code(String code){
this.setErrorCode(code);
return this;
}
public R data(Object data){
this.setData(data);
return this;
}
}
package com.yuan.dto;
public interface ResultCode {
public static String SUCCESS = "00000"; //成功
public static String ERROR = "20001"; //失败
}
handler包
/**
* 自动填充
* @author 111
*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("creationDate",new Date(),metaObject);
this.setFieldValByName("modifyDate",new Date(),metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("modifyDate",new Date(),metaObject);
}
}
@Configuration
public class PageConfig {
// 配置分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptorPage() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
pojo包
/**
* 新闻表
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "news")
public class News {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 新闻类型
*/
@TableField(value = "type_id")
private String typeId;
/**
* 新闻标题
*/
@TableField(value = "title")
private String title;
/**
* 发布者
*/
@TableField(value = "publisher")
private String publisher;
/**
* 发布者头像
*/
@TableField(value = "publisher_face")
private String publisherFace;
/**
* 新闻内容
*/
@TableField(value = "content")
private String content;
/**
* 发布时间
*/
@TableField(value = "create_time")
private Date createTime;
/**
* 修改时间
*/
@TableField(value = "update_time")
private Date updateTime;
/**
* 删除状态
*/
@TableField(value = "deleted")
private Integer deleted;
@TableField(value = "typeId")
private String typeid;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "`type`")
public class Type {
@TableId(value = "id", type = IdType.INPUT)
private String id;
@TableField(value = "name")
private String name;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "users")
public class Users {
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 姓名
*/
@TableField(value = "u_user_name")
private String uUserName;
/**
* 年龄
*/
@TableField(value = "age")
private Integer age;
/**
* 优先
*/
@TableField(value = "u_email")
private String uEmail;
@TableField(value = "password")
private String password;
}
Vo包
/**
* @author mo
* @Description: token响应对象
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "Token响应对象")
@Builder
public class TokenVo implements Serializable {
private static final long serialVersionUID = 1959653550240034417L;
@ApiModelProperty(value = "用户认证凭证")
private String token;
@ApiModelProperty(value = "过期时间,单位毫秒")
private Long expTime;
@ApiModelProperty(value = "生成时间,单位毫秒")
private Long genTime;
}
yuan-dao数据访问层模块
依赖
</properties>
<dependencies>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.yuan-utils</groupId>
<artifactId>yuan-utils</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.yuan-bean</groupId>
<artifactId>yuan-beans</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
mapper
package com.yuan.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yuan.pojo.News;
public interface NewsMapper extends BaseMapper<News> {
}
package com.yuan.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yuan.pojo.Type;
public interface TypeMapper extends BaseMapper<Type> {
}
package com.yuan.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yuan.pojo.Users;
public interface UsersMapper extends BaseMapper<Users> {
}
由于是单表查询XML文件没有写SQL的配置使用Mybatis-plus
service
package com.yuan.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yuan.pojo.News;
public interface NewsService extends IService<News> {
}
package com.yuan.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yuan.mapper.NewsMapper;
import com.yuan.pojo.News;
import com.yuan.service.NewsService;
import org.springframework.stereotype.Service;
@Service
public class NewsServiceImpl extends ServiceImpl<NewsMapper, News> implements NewsService {
}
package com.yuan.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yuan.pojo.Type;
public interface TypeService extends IService<Type> {
}
package com.yuan.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yuan.mapper.TypeMapper;
import com.yuan.pojo.Type;
import com.yuan.service.TypeService;
import org.springframework.stereotype.Service;
@Service
public class TypeServiceImpl extends ServiceImpl<TypeMapper, Type> implements TypeService {
}
package com.yuan.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yuan.pojo.Users;
public interface UsersService extends IService<Users> {
}
package com.yuan.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yuan.mapper.UsersMapper;
import com.yuan.pojo.Users;
import com.yuan.service.UsersService;
import org.springframework.stereotype.Service;
@Service
public class UsersServiceImpl extends ServiceImpl<UsersMapper, Users> implements UsersService {
}
yuan-login登录模块
依赖
<dependencies>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--druid 数据源 阿里巴巴提供的一款性能非常高的数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.yuan-dao</groupId>
<artifactId>yuan-dao</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.yuan-utils</groupId>
<artifactId>yuan-utils</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.yuan-bean</groupId>
<artifactId>yuan-beans</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${jwt-version}</version>
</dependency>
</dependencies>
application.properties
#配置数据源 druid springboot默认的数据源 HikariDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/newsdb?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=root
#指定数据源的类型
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#日志的实现类
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#已删除的值
mybatis-plus.global-config.db-config.logic-delete-value=1
#未删除的值
mybatis-plus.global-config.db-config.logic-not-delete-value=0
mybatis-plus.mapper-locations=classpath:com/yuan/mapper/*.xml
server.port=8081
spring.application.name=yuan-login
SecurityConfig
/**
* Spring Security
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//授权
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/user/**").permitAll();
// .antMatchers("/news/**").hasRole("vip");
http.formLogin().loginPage("/user/login");
http.csrf().disable();//关闭//csrf功能 登出失败的可能的原因
http.rememberMe();
}
// //认证
// //BCryptPasswordEncoder密码加密算法
// // .withUser("用户名").password(new BCryptPasswordEncoder().encode("密码")).roles("权限访问的页面")
// @Override
// protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
// .withUser("李四").password("123456").roles("vip");
// }
}
UserController
package com.yuan.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yuan.dto.R;
import com.yuan.dto.ResultCode;
import com.yuan.pojo.Users;
import com.yuan.service.UsersService;
import com.yuan.utils.JWTUtil;
import com.yuan.vo.TokenVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
@RestController
public class UserController {
@Autowired
private UsersService usersService;
@RequestMapping("/user/login")
public String login(){
return "login";
}
private static final long EXPRIVE_TIME=30*60*1000;
@RequestMapping("/user/doLogin")
@ResponseBody
public Object doLogin(@RequestParam("username") String username, @RequestParam("password") String password,
HttpServletResponse response
) throws IOException {
QueryWrapper<Users> queryWrapper = new QueryWrapper<Users>();
queryWrapper.eq("u_user_name",username);
queryWrapper.eq("password",password);
Users users = usersService.getOne(queryWrapper);
// if (users != null) {
// String token = JWTUtil.sign(users.getUUserName(), users.getPassword());
// response.setHeader("token",token);
// return token;
// }
if (users != null) {
String token = JWTUtil.sign(users.getUUserName(), String.valueOf(users.getId()));
TokenVo tokenVo=TokenVo.builder()
.token(token)
.genTime(System.currentTimeMillis())
.expTime(System.currentTimeMillis()+EXPRIVE_TIME).build();
R data = R.ok().data(tokenVo);
//转json
String tokenVoStr = new ObjectMapper().writeValueAsString(data);
//设置编码格式
response.setContentType("application/json;charset=utf-8");
//通过response
response.setHeader("token", token);
response.setHeader("Access-Control-Expose-Headers","token");
PrintWriter out= new PrintWriter(new OutputStreamWriter(response.getOutputStream()));
out.write(tokenVoStr);
out.flush();
out.close();
}
return ResultCode.ERROR;
}
}
启动类
@SpringBootApplication
@MapperScan("com.yuan.mapper")
public class YuanLoginApplication {
public static void main(String[] args) {
SpringApplication.run(YuanLoginApplication.class, args);
}
}
yuan-biz页面模块
依赖
<dependencies>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--druid 数据源 阿里巴巴提供的一款性能非常高的数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<dependency>
<groupId>com.yuan-bean</groupId>
<artifactId>yuan-beans</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.yuan-dao</groupId>
<artifactId>yuan-dao</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.yuan-utils</groupId>
<artifactId>yuan-utils</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
NewsController
package com.yuan.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yuan.pojo.News;
import com.yuan.service.NewsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
public class NewsController {
@Autowired
private NewsService newsService;
/**
* 查询接口
* @param pageNum
* @param pageSize
* @return
*/
@RequestMapping("/news/list")
@ResponseBody
public Object list(@RequestParam(value = "pageNum",defaultValue = "1",required = false) Integer pageNum,
@RequestParam(value = "pageSize", defaultValue = "3", required = false) Integer pageSize) {
Page<News> page = new Page<>(pageNum, pageSize);
Page<News> userPage = newsService.page(page);
return userPage;
}
/**
* 新增
* @param news
* @return
*/
@RequestMapping("/news/add")
public Object NewsAdd(@RequestBody News news){
return newsService.save(news);
}
/**
* 删除
* @param id
* @return
*/
@RequestMapping("/news/delete")
public Object delete(@RequestParam("id") Integer id){
return newsService.removeById(id);
}
/**
* 修改
* @param news
* @return
*/
@RequestMapping("/news/update")
public boolean update(@RequestBody News news) {
QueryWrapper<News> queryWrapper = new QueryWrapper<News>();
queryWrapper.eq("id",news.getId());
return newsService.update(news,queryWrapper);
}
}
application.properties
#配置数据源 druid springboot默认的数据源 HikariDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/newsdb?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=root
#指定数据源的类型
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#日志的实现类
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#已删除的值
mybatis-plus.global-config.db-config.logic-delete-value=1
#未删除的值
mybatis-plus.global-config.db-config.logic-not-delete-value=0
mybatis-plus.mapper-locations=classpath:com/yuan/mapper/*.xml
spring.application.name=yuan-biz
server.port=8082
启动类
@SpringBootApplication
@MapperScan("com.yuan.mapper")
public class YuanBizApplication {
public static void main(String[] args) {
SpringApplication.run(YuanBizApplication.class, args);
}
}
yuan-gateway网关模块
依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--swagger start-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
</dependency>
</dependencies>
application.yaml
spring:
application:
name: yuan-gateway
cloud:
#注册到服务中心
nacos:
discovery:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
gateway:
routes:
#路由Id
- id: yuan-biz
#路由目标的服务名
uri: http://localhost:8082/
#路径匹配
predicates:
- Path=/news/** #路径包含就转发,否则不转发
#路由Id
- id: yuan-login
#路由目标的服务名
uri: http://localhost:8081/
#路径匹配
predicates:
- Path=/user/** #路径包含就转发,否则不转发
server:
port: 9999
CorsConfig
/**
* 跨域允许
*/
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); // 允许cookies跨域
config.addAllowedOrigin("*");// #允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
config.addAllowedHeader("*");// #允许访问的头信息,*表示全部
config.setMaxAge(18000L);// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
config.addAllowedMethod("OPTIONS");// 允许提交请求的方法类型,*表示全部允许
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
config.addExposedHeader("token");
config.addExposedHeader("Access-Control-Allow-Origin");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
CheckTokenFilter
@Slf4j
@Component
public class CheckTokenFilter implements GlobalFilter {
/*
ServerWebExchange命名为服务网络交换器,存放着重要的请求-响应属性、请求实例和响应实例等等
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获取响应对象
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
//获取token
String token = request.getHeaders().getFirst("token");
log.info("========>CheckTokenFilter");
//设置不需要验证token的请求
List<String> excludeUrl = Arrays.asList(
"/user/login",
"/user/doLogin"
);
//获取请求路径
String path = request.getPath().toString();
log.info("path===>"+path);
if(excludeUrl.contains(path)){
//放行
return chain.filter(exchange);
}
//验证token
// String token = request.getHeaders().getFirst("token");
log.info("token===>"+token);
if(!StringUtils.hasText(token)){
return getVoidMono(response, R.error().message("token为空"));
}
if(!JWTUtil.verify(token)){
return getVoidMono(response,R.error().message("token不合法"));
}
//放行
return chain.filter(exchange);
}
private Mono<Void> getVoidMono(ServerHttpResponse response,R r){
response.getHeaders().add("Content-Type","application/json;charset=utf-8");
String tokenEmpty = null;
DataBuffer dataBuffer = null;
try {
tokenEmpty = new ObjectMapper().writeValueAsString(r);
dataBuffer = response.bufferFactory().wrap(tokenEmpty.getBytes());
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return response.writeWith(Flux.just(dataBuffer));
}
}
启动类
@SpringBootApplication
@EnableDiscoveryClient
public class YuanGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(YuanGatewayApplication.class, args);
}
}
dto
/**
* @author mo
* @Description: Jwt工具类,用于生成和校验jwt
*/
public class JWTUtil {
/**过期时间为半小时*/
private static final long EXPRIE_TIME = 30*60*1000;
/**加密时的密钥,不能泄露*/
private static final String secret = "B8B7EDFB-53AC-2BD2-E804-461A0C961F29";
/**
* 校验用户名和密码
* @param token
* @param username
* @return
*/
public static boolean verify(String token) {
try {
// 加密方式必须与生成token时的加密方式一致
Algorithm algorithm = Algorithm.HMAC512(secret);
JWTVerifier verifier = JWT.require(algorithm)
.build();
verifier.verify(token);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 获取token中的信息无需secret解密也能获得
* @param token
* @return
*/
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
return null;
}
}
/**
* 生成JWTToken自定义 ItripUserDetails 用于封装用户信息
* @param username
* @return
* @throws UnsupportedEncodingException
*/
public static String sign(String username) throws
UnsupportedEncodingException {
Date date = new Date(System.currentTimeMillis()+EXPRIE_TIME);
Algorithm algorithm = Algorithm.HMAC512(secret);
// 附带username信息
return JWT.create()
.withClaim("username", username)
.withExpiresAt(date)
.sign(algorithm);
}
}
@Data
public class R {
@ApiModelProperty(value = "是否成功标示,true:成功;false:失败")
private String success;
@ApiModelProperty(value = "异常码")
private String errorCode;
@ApiModelProperty(value = "响应提示信息")
private String msg;
@ApiModelProperty(value = "返回数据")
private Object data;
//把构造方法私有
private R(){}
//返回集合List return R.ok().data("list",list)
//登录成功 return R.ok().message("登录成功");
//登录失败(密码错误) return R.error().code(10000).message("密码错误") //链式编程
//成功静态方法
public static R ok(){
R r = new R();
r.setSuccess("true");
r.setErrorCode(ResultCode.SUCCESS);
r.setMsg("成功");
return r;
}
//失败静态方法
public static R error(){
R r = new R();
r.setSuccess("false");
r.setErrorCode(ResultCode.ERROR);
r.setMsg("失败");
return r;
}
public R success(String success){
this.setSuccess(success);
return this;
}
public R message(String message){
this.setMsg(message);
return this;
}
public R code(String code){
this.setErrorCode(code);
return this;
}
public R data(Object data){
this.setData(data);
return this;
}
}
public interface ResultCode {
public static String SUCCESS = "00000"; //成功
public static String ERROR = "20001"; //失败
}
前端Vue
下载依赖
#安装cookies
npm install vue-cookies --save
#安装axios
npm install --save axios vue-axios
#安装elementUI
npm i element-ui -S
#安装路由
#npm install vue-router@3
npm install vue-router@3 or 4
main.js
import Vue from 'vue'
import App from './App'
import router from "./router/index"
Vue.config.productionTip = false
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import axios from "axios";
import request from "./router/request"
//注册axios为全局的变量
Vue.prototype.$axios=axios;
Vue.use(ElementUI)
//导入拦截,注册未全部变量
Vue.prototype.$request=request;
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
request,
components: { App },
template: '<App/>'
})
App.vue
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
components: {
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
router目录下index.js
import Vue from "vue"
import Router from "vue-router";
Vue.use(Router);
const router=new Router({
routes:[
{
name:"Login",
path:'/',
component: ()=>import("@/components/Login")
},
{
name:"Main",
path: "/Main",
component:()=>import("@/components/Main")
}
]
});
//配置导航守卫
router.beforeEach((to, from,next) => {
console.log("to======>"+to.path);
console.log("from======>"+from.path);
const isLogin = true;
if(to.path!=="Login"){
if(isLogin){
next();
}
return false;
}
return next();
});
export default router;
request.js
import axios from "axios";
//导入cookie
import cookie from "js-cookie"
/*创建axios实例*/
const intance=axios.create({
baseURL : "http://localhost:9999",
timeout:10000
})
/*请求的拦截*/
intance.interceptors.request.use(function (config) {
config.headers.token=cookie.get("token")
return config;
},function (error) {
console.log(error)
return Promise.reject(error)
})
/*响应的拦截器*/
intance.interceptors.response.use(function (response) {
const res=response.data
return res;
},function (error) {
return Promise.reject(error)
})
export default intance;
Login.vue
<template>
<div style="width: 300px">
<el-form ref="loginForm" :model="form" :rules="rules" label-width="80px" class="login-box">
<h3 class="login-title">欢迎登录</h3>
<el-form-item label="账号" prop="username">
<el-input type="text" placeholder="请输入账号" v-model="form.username"/>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" placeholder="请输入密码" v-model="form.password"/>
</el-form-item>
<el-form-item>
<el-button type="primary" v-on:click="onsubmit('loginForm')">登录</el-button>
</el-form-item>
</el-form>
<el-dialog title="温馨提示" width="30%">
<span>请输入账号和密码</span>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="dialogVisible = false">确定</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
import cookie from "js-cookie"
export default {
name: "Login",
data(){
return{
form:{
username:'',
password:'',
},
//表单验证,需要在 el-form-item 元素中增加prop属性
rules:{
username:[
{required:true,message:"账号不可为空",trigger:"blur"}
],
password:[
{required:true,message:"密码不可为空",tigger:"blur"}
]
},
//对话框显示和隐藏
dialogVisible:false
}
},
methods:{
onsubmit(formName){
//为表单绑定验证功能
this.$refs[formName].validate((valid)=>{
if(valid){
this.$request.post("/user/doLogin?username="+this.form.username+"&password="+this.form.password)
.then((response)=>{
if (response!=="20001"){
// console.log(response.data.token)
// 第一个参数存储的token名,第二个参数是服务器返回的token值,第三个参数设置token的作用域
cookie.set("token",response.data.token,{admin:"localhost"})//设置token
console.log(cookie.get("token"))
this.$router.push('/Main');
}else {
alert(response.success)
alert("用户名或密码不正确")
}
})
}else{
this.dialogVisible=true;
return false;
}
});
}
}
}
</script>
<style scoped>
</style>
Main.vue
<template>
<div>
<h2>新闻列表</h2>
<el-row class="newssavebtn">
<el-button type="primary" @click="newsAdd()" icon="el-icon-plus" >新增</el-button>
</el-row>
<el-table
:data="newsList"
style="width: 100%">
<el-table-column
prop="title"
label="标题"
width="180">
</el-table-column>
<el-table-column
prop="publisher"
label="发布人">
</el-table-column>
<el-table-column
prop="createTime"
label="发布时间">
</el-table-column>
<el-table-column label="操作">
<!--template slot-scope="scope":存的是后台的数据-->
<template slot-scope="scope">
<!--scope.$index:获取索引,
scope.row:获取所有数据
scope.row.id:获取id,如果实现删除功能就写 @click="handleDelete(scope.row.id)
-->
<el-button
size="mini"
@click="handleEdit(scope.$index, scope.row)">编辑</el-button>
<el-button
size="mini"
type="danger"
@click="newsDelete(scope.row.id)">删除</el-button><!--scope.row.id:获取删除新闻id-->
</template>
</el-table-column>
</el-table>
<!--分页-->
<el-pagination
background
layout="prev, pager, next"
@current-change="handPage"
:page-size="pageSize"
:current-page="pageNum"
:total="total">
</el-pagination>
<!--新增和修改弹窗-->
<el-dialog
title="提示"
:visible.sync="dialogVisible"
width="30%">
<span>这是一段信息</span>
<el-form ref="form" :model="news" label-width="80px">
<el-form-item label="新闻标题">
<el-input v-model="news.title"></el-input>
</el-form-item>
<el-form-item label="新闻内容">
<el-input type="textarea" v-model="news.content"></el-input>
</el-form-item>
<el-form-item label="发布人">
<el-input v-model="news.publisher"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit(news)" icon="el-icon-plus">提交</el-button>
<el-button>取消</el-button>
</el-form-item>
</el-form>
</el-dialog>
</div>
</template>
<script>
import cookie from "js-cookie"
export default {
name: "Main",
data(){
return{
newsList:[],
dialogVisible:false,//控制弹窗显示和隐藏
news:{},
pageSize:5,
pageNum:1,
total:0
}
},
methods:{
//分页查询
getNewsPage(){
this.$request.post("/news/list?pageNum="+this.pageNum+"&pageSize="+this.pageSize+"").then((response)=>{
console.log(response+"\t"+this.pageNum+"\t"+this.pageSize)
//将查询的数据放到newsList中
this.newsList=response.records
this.total=response.total;
})
.catch(function (error) {
console.log("=====>"+error)
})
},
//当前页面改变重新查数据 点击上一页,下一页
handPage(pageNum){
this.pageNum=pageNum
this.getNewsPage()
},
//处理新增
newsAdd(){
this.dialogVisible = true;
},
//新增新闻
newsAdds(){
this.$request.post('/news/add', this.news)
.then((response)=> {
if (response===true){
this.$message({
message: '新增成功',
type: 'success'
})
}
})
},
//查询要修改的数据,从前端的row中获取
handleEdit($index,row){
this.dialogVisible=true
this.news = row
},
// 定义提交事件判断是新增还是修改
onSubmit:function (news) {
if (news.id!=null){
this.newsUpdate();
this.news={};
}else {
this.newsAdds();
this.news={};
this.dialogVisible = false
}
},
//修改方法
newsUpdate(){
this.$request.post('/news/update', this.news)
.then((response)=> {
if (response===true){
this.$message({
message: '修改成功',
type: 'success'
})
this.getNewsPage()
}
})
//隐藏模态框
this.dialogVisible={};
this.dialogVisible = false
},
//删除操作
newsDelete(id){
this.$confirm('此操作将永久删除该文件, 是否继续?', '提示',{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$request.post("/news/delete?id="+id+"")
.then((res)=>{
if (res===true){
this.$message({
message: '删除成功',
type: 'success'
})
this.getNewsPage()
}else {
this.$message({
message: '删除失败',
type: 'success'
})
}
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
}
},
mounted() {
//默认时展示数据
//this.getNewsList();
//分页数据展示
this.getNewsPage()
//隐藏模态框
this.dialogVisible=false;
}
}
</script>
<style scoped>
</style>