1. 使用者注冊
1. 接口
POST http://localhost:10010/web-service/user/register
{
"mobile":"13612345677",
"password":"1234",
"username":"jack3",
"code":"3919"
}
1. 後端
1. 儲存前需要再次進行服務端校驗
1. 使用者名是否注冊
2. 手機号是否注冊
3. 驗證碼是否失效
4. 驗證碼是否錯誤
2. 密碼需要使用 BCrypt進行加密
3. 步驟一:修改UserService接口,添加register方法
/**
* 使用者注冊
* @param user
* @return
*/
public boolean register(User user) ;
1. 步驟二:完善UserServiceImpl實作類
@Override
public boolean register(User user) {
//密碼加密
String newPassword = BCrypt.hashpw(user.getPassword());
user.setPassword(newPassword);
//處理資料
user.setCreatedAt(new Date());
user.setUpdatedAt(user.getCreatedAt());
int insert = baseMapper.insert(user);
return insert == 1;
}
1. 步驟三:修改UserController,添加register方法
/**
* 使用者注冊
* @param user
* @return
*/
@PostMapping("/register")
public BaseResult register(@RequestBody User user){
//服務端校驗
User findUser = userService.findByUsername(user.getUsername());
if(findUser != null) {
return BaseResult.error("使用者名已經存在");
}
findUser = userService.findByMobile(user.getMobile());
if(findUser != null) {
return BaseResult.error("電話号碼已經存在");
}
//驗證碼
String code = stringRedisTemplate.opsForValue().get("sms_register" + user.getMobile());
//删除redis中的驗證碼
stringRedisTemplate.delete("sms_register" + user.getMobile());
if(code == null) {
return BaseResult.error("驗證碼失效");
}
if(!code.equals(user.getCode())) {
return BaseResult.error("驗證碼不正确");
}
//注冊
boolean register = userService.register(user);
if(register) {
return BaseResult.ok("注冊成功");
}
return BaseResult.error("注冊失敗");
}
1. 日期處理(可選)
1. 編寫 DateMetaObjectHandler 用于處理“建立時間”和“修改日期”
package com.czxy.changgou4.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @author 桐叔
* @email [email protected]
*/
@Component
public class DateMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createdAt", new Date(), metaObject);
this.setFieldValByName("updatedAt", new Date(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updatedAt", new Date(), metaObject);
}
}
1. 完善User JavaBean,設定填充方式
@TableField(value="created_at",fill = FieldFill.INSERT)
private Date createdAt;
@TableField(value="updated_at",fill = FieldFill.INSERT_UPDATE)
private Date updatedAt;
1. 前端
1. 步驟一:修改 api.js ,添加注冊函數
//注冊
register : ( user )=> {
return axios.post('/web-service/user/register', user )
}
1. 步驟二:處理表單,驗證碼input綁定資料,送出按鈕綁定事件
<li class="checkcode">
<label for="">驗證碼:</label>
<input type="text" name="checkcode" v-model="user.code" />
<button :disabled="btnDisabled" @click.prevent="sendSmsFn" >
發送驗證碼<span v-show="btnDisabled">{{seconds}}秒</span>
</button>
<p :class="userMsg.smsData.code == 1 ? 'success' : 'error'">{{userMsg.smsData.message}} </p>
</li>
<li>
<label for=""> </label>
<input type="checkbox" class="chb" checked="checked" /> 我已閱讀并同意《使用者注冊協定》
</li>
<li>
<label for=""> </label>
<input type="submit" value="" @click.prevent="registerFn" class="login_btn" />
</li>
- 步驟三:完善data區域的user資料
user : { //表單封裝資料
username : "", //使用者名
mobile : "13699282444", //手機号
password : "", //密碼
code : "" //驗證碼
},
- 步驟四:編寫registerFn函數
async registerFn() {
let { data } = await this.$request.register( this.user )
if( data.code == 20000) {
//成功
this.$router.push('/login')
} else {
//失敗--與發送驗證碼使用一個位置顯示錯誤資訊
this.userMsg.smsData = data
}
}
- 整合JWT
- 整合分析
- 生成token:在使用者登入成功,根據使用者的登入資訊,生成登入辨別token,并傳回給浏覽器。
- 使用token:完善ajax請求,在請求之前添加請求頭,設定token
- 校驗token:在網關中編寫過濾器,進行請求進行攔截,并校驗token。
- 白名單:在白名單中的請求,是不需要token可以直接通路的。
- 生成Token
- 使用者登入成功,生成token,并将token響應給浏覽器。(認證服務 AuthService)
- 步驟一:檢視 application.yml檔案,确定 jwt配置資訊
- 步驟二:建立JwtProperties檔案,用于加載sc.jwt配置資訊
package com.czxy.changgou4.config;
import com.czxy.changgou4.utils.RsaUtils;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.File;
import java.security.PrivateKey;
import java.security.PublicKey;
/**
* @author 桐叔
* @email [email protected]
*/
@Data
@ConfigurationProperties(prefix = "sc.jwt")
@Component
public class JwtProperties {
private String secret; // 密鑰
private String pubKeyPath;// 公鑰
private String priKeyPath;// 私鑰
private int expire;// token過期時間
private PublicKey publicKey; // 公鑰
private PrivateKey privateKey; // 私鑰
private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class);
@PostConstruct
public void init(){
try {
File pubFile = new File(this.pubKeyPath);
File priFile = new File(this.priKeyPath);
if( !pubFile.exists() || !priFile.exists()){
RsaUtils.generateKey( this.pubKeyPath ,this.priKeyPath , this.secret);
}
this.publicKey = RsaUtils.getPublicKey( this.pubKeyPath );
this.privateKey = RsaUtils.getPrivateKey( this.priKeyPath );
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
}
1. 步驟三:修改AuthController,注入JwtProperties,并使用JwtUtils生成token
package com.czxy.changgou4.controller;
/**
* @author 桐叔
* @email [email protected]
*/
import com.czxy.changgou4.config.JwtProperties;
import com.czxy.changgou4.domain.AuthUser;
import com.czxy.changgou4.service.AuthService;
import com.czxy.changgou4.utils.JwtUtils;
import com.czxy.changgou4.vo.BaseResult;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* Created by liangtong.
*/
@RestController
@RequestMapping("/auth")
public class AuthController {
@Resource
private AuthService authService;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private JwtProperties jwtProperties;
@PostMapping("/login")
public BaseResult login(@RequestBody AuthUser user){
//校驗驗證碼--使用後删除
String redisCode = stringRedisTemplate.opsForValue().get( "login" + user.getUsername() );
stringRedisTemplate.delete( "login" + user.getUsername() );
if(redisCode == null) {
return BaseResult.error("驗證碼無效");
}
if(! redisCode.equalsIgnoreCase(user.getCode())) {
return BaseResult.error("驗證碼錯誤");
}
//登入
AuthUser loginUser = authService.login(user);
if(loginUser != null ) {
//生成Token
String token = JwtUtils.generateToken(loginUser, jwtProperties.getExpire(), jwtProperties.getPrivateKey());
return BaseResult.ok("登入成功").append("loginUser",loginUser).append("token", token);
} else {
return BaseResult.error("使用者名或密碼不比對");
}
}
}
- 使用token
- 步驟一:登入成功後儲存token,修改 Login.vue頁面
async loginFn() {
let { data } = await this.$request.login( this.user )
if( data.code == 20000) {
//成功
sessionStorage.setItem('user' , JSON.stringify(data.other.loginUser) )
//儲存token
sessionStorage.setItem('token' , data.other.token )
//跳轉到首頁
this.$router.push('/')
} else {
this.errorMsg = data.message
}
}
- 步驟二:請求是自動攜帶token,修改apiclient.js,将token添加到請求頭
//參考 https://axios.nuxtjs.org/helpers
let token = sessionStorage.getItem('token')
if( token ) {
// Adds header: `Authorization: 123` to all requests
// this.$axios.setToken('123')
$axios.setToken( token )
}
1. 步驟三:檢查 nuxt.conf.js,插件模式改成“client”
1. 否則抛異常“sessionStorage is not defined”
plugins: [
{ src: '~plugins/apiclient.js', mode: 'client' }
],
- 校驗token
- token的校驗在網關項目處完成
- 步驟一:修改application.yml添加jwt配置
#自定義内容
sc:
jwt:
secret: sc@Login(Auth}*^31)&czxy% # 登入校驗的密鑰
pubKeyPath: D:/rsa/rsa.pub # 公鑰位址
priKeyPath: D:/rsa/rsa.pri # 私鑰位址
expire: 360 # 過期時間,機關分鐘
- 步驟二:建立 JwtProperties,用于加載配置檔案
package com.czxy.changgou4.config;
import com.czxy.changgou4.utils.RsaUtils;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.File;
import java.security.PrivateKey;
import java.security.PublicKey;
/**
* @author 桐叔
* @email [email protected]
*/
@Data
@ConfigurationProperties(prefix = "sc.jwt")
public class JwtProperties {
private String secret; // 密鑰
private String pubKeyPath;// 公鑰
private String priKeyPath;// 私鑰
private int expire;// token過期時間
private PublicKey publicKey; // 公鑰
private PrivateKey privateKey; // 私鑰
private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class);
@PostConstruct
public void init(){
try {
File pubFile = new File(this.pubKeyPath);
File priFile = new File(this.priKeyPath);
if( !pubFile.exists() || !priFile.exists()){
RsaUtils.generateKey( this.pubKeyPath ,this.priKeyPath , this.secret);
}
this.publicKey = RsaUtils.getPublicKey( this.pubKeyPath );
this.privateKey = RsaUtils.getPrivateKey( this.priKeyPath );
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
}
- 步驟三:編寫過濾器,對所有路徑進行攔截
package com.czxy.changgou4.filter;
import com.czxy.changgou4.config.FilterProperties;
import com.czxy.changgou4.config.JwtProperties;
import com.czxy.changgou4.pojo.User;
import com.czxy.changgou4.utils.JwtUtils;
import com.czxy.changgou4.utils.RsaUtils;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
/**
* @author 桐叔
* @email [email protected]
*/
@Component
public class LoginFilter implements GlobalFilter, Ordered {
@Resource
private JwtProperties jwtProperties;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1 獲得請求路徑
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
System.out.println(path);
//2 白名單放行
//3 獲得token
String token = request.getHeaders().getFirst("Authorization");
//4 校驗token
try {
JwtUtils.getObjectFromToken(token, RsaUtils.getPublicKey(jwtProperties.getPubKeyPath()), User.class);
return chain.filter(exchange);
} catch (Exception e) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
DataBuffer wrap = response.bufferFactory().wrap("沒有權限".getBytes(StandardCharsets.UTF_8));
return exchange.getResponse().writeWith(Flux.just(wrap));
}
}
@Override
public int getOrder() {
return 1;
}
}
- 步驟四:修改前端 apiclient.js 檔案,用于處理401異常
//處理響應異常
$axios.onError(error => {
// token失效,伺服器響應401
if(error.response.status === 401) {
console.error(error.response.data)
redirect('/login')
}
})
- api.js 完整代碼
var axios = null
export default ({ $axios, redirect, process }, inject) => {
//參考 https://axios.nuxtjs.org/helpers
let token = sessionStorage.getItem('token')
if( token ) {
// Adds header: `Authorization: 123` to all requests
// this.$axios.setToken('123')
$axios.setToken( token )
}
//處理響應異常
$axios.onError(error => {
// token失效,伺服器響應401
if(error.response.status === 401) {
console.error(error.response.data)
redirect('/login')
}
})
//指派
axios = $axios
//4) 将自定義函數交于nuxt
// 使用方式1:在vue中,this.$request.xxx()
// 使用方式2:在nuxt的asyncData中,content.app.$request.xxx()
inject('request', request)
}
- 白名單
- 不需要攔截的資源都配置到yml檔案中,在過濾器直接放行
- 步驟一:修改application.yml檔案
#自定義内容
sc:
jwt:
secret: sc@Login(Auth}*^31)&czxy% # 登入校驗的密鑰
pubKeyPath: D:/rsa/rsa.pub # 公鑰位址
priKeyPath: D:/rsa/rsa.pri # 私鑰位址
expire: 360 # 過期時間,機關分鐘
filter:
allowPaths:
- /checkusername
- /checkmobile
- /sms
- /register
- /login
- /verifycode
- /categorys
- /news
- /brands
- /specifications
- /search
- /goods
- /comments
- swagger
- /api-docs
- 步驟二:建立FilterProperties配置檔案,用于存放允許放行的路徑
package com.czxy.changgou4.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
* @author 桐叔
* @email [email protected]
*/
@Data
@ConfigurationProperties(prefix="sc.filter")
public class FilterProperties {
//允許通路路徑集合
private List<String> allowPaths;
}
- 步驟三:修改 LoginFilter,放行名單中配置的路徑
package com.czxy.changgou4.filter;
import com.czxy.changgou4.config.FilterProperties;
import com.czxy.changgou4.config.JwtProperties;
import com.czxy.changgou4.pojo.User;
import com.czxy.changgou4.utils.JwtUtils;
import com.czxy.changgou4.utils.RsaUtils;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
/**
* @author 桐叔
* @email [email protected]
*/
@Component
//2.1 加載JWT配置類
@EnableConfigurationProperties({FilterProperties.class} ) //加載配置類
public class LoginFilter implements GlobalFilter, Ordered {
@Resource
private FilterProperties filterProperties;
@Resource
private JwtProperties jwtProperties;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1 獲得請求路徑
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
System.out.println(path);
//2 白名單放行
for (String allowPath : filterProperties.getAllowPaths()) {
//判斷包含
if(path.contains(allowPath)){
return chain.filter(exchange);
}
}
//3 獲得token
String token = request.getHeaders().getFirst("Authorization");
//4 校驗token
try {
JwtUtils.getObjectFromToken(token, RsaUtils.getPublicKey(jwtProperties.getPubKeyPath()), User.class);
return chain.filter(exchange);
} catch (Exception e) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
DataBuffer wrap = response.bufferFactory().wrap("沒有權限".getBytes(StandardCharsets.UTF_8));
return exchange.getResponse().writeWith(Flux.just(wrap));
}
}
@Override
public int getOrder() {
return 1;
}
}