天天看點

【暢購商城】使用者注冊以及整合JWT

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>      
  1. 步驟三:完善data區域的user資料
【暢購商城】使用者注冊以及整合JWT
user : {  //表單封裝資料
        username : "",          //使用者名
        mobile : "13699282444", //手機号
        password : "",          //密碼
        code : ""               //驗證碼
      },      
  1. 步驟四:編寫registerFn函數
async registerFn() {
let { data } = await this.$request.register( this.user )
if( data.code == 20000) {
//成功
this.$router.push('/login')
      } else {
//失敗--與發送驗證碼使用一個位置顯示錯誤資訊
this.userMsg.smsData = data
      }
    }      
  1. 整合JWT
  1. 整合分析
【暢購商城】使用者注冊以及整合JWT
  1. 生成token:在使用者登入成功,根據使用者的登入資訊,生成登入辨別token,并傳回給浏覽器。
  2. 使用token:完善ajax請求,在請求之前添加請求頭,設定token
  3. 校驗token:在網關中編寫過濾器,進行請求進行攔截,并校驗token。
  4. 白名單:在白名單中的請求,是不需要token可以直接通路的。
  1. 生成Token
  1. 使用者登入成功,生成token,并将token響應給浏覽器。(認證服務 AuthService)
  2. 步驟一:檢視 application.yml檔案,确定 jwt配置資訊
【暢購商城】使用者注冊以及整合JWT
  1. 步驟二:建立JwtProperties檔案,用于加載sc.jwt配置資訊
【暢購商城】使用者注冊以及整合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("使用者名或密碼不比對");
        }
    }
}      
  1. 使用token
  1. 步驟一:登入成功後儲存token,修改 Login.vue頁面
【暢購商城】使用者注冊以及整合JWT
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
      }
    }      
  1. 步驟二:請求是自動攜帶token,修改apiclient.js,将token添加到請求頭
【暢購商城】使用者注冊以及整合JWT
//參考 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' }
  ],      
  1. 校驗token
  1. token的校驗在網關項目處完成
  2. 步驟一:修改application.yml添加jwt配置
【暢購商城】使用者注冊以及整合JWT

#自定義内容

sc:

  jwt:

    secret: sc@Login(Auth}*^31)&czxy% # 登入校驗的密鑰

    pubKeyPath: D:/rsa/rsa.pub # 公鑰位址

    priKeyPath: D:/rsa/rsa.pri # 私鑰位址

    expire: 360 # 過期時間,機關分鐘

  1. 步驟二:建立 JwtProperties,用于加載配置檔案
【暢購商城】使用者注冊以及整合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")
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. 步驟三:編寫過濾器,對所有路徑進行攔截
【暢購商城】使用者注冊以及整合JWT
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;
    }
}      
  1. 步驟四:修改前端 apiclient.js 檔案,用于處理401異常
【暢購商城】使用者注冊以及整合JWT
//處理響應異常
  $axios.onError(error => {
// token失效,伺服器響應401
if(error.response.status === 401) {
      console.error(error.response.data)
      redirect('/login')
    }
  })      
  1. 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)
}      
  1. 白名單
  1. 不需要攔截的資源都配置到yml檔案中,在過濾器直接放行
  2. 步驟一:修改application.yml檔案
【暢購商城】使用者注冊以及整合JWT

#自定義内容

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      
  1. 步驟二:建立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;
}      
  1. 步驟三:修改 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;
    }
}