微人事(一)登入模組

Johnny_牧雲發表於2020-12-26

(一)前後端分離後,前後端的資料互動關鍵橋樑—代理物件
該專案是屬於前後端分離的專案,因此前端和後端各有一個埠建立socket請求。前端採用8081埠,後端使用8080埠。為了使前端能夠呼叫後端的介面,後端能把JSON串傳回前端。就需要在前端建立一個代理物件,進行埠的對映。程式碼如下:

埠對映vue.config.js
後端使用8081埠,前端使用8080埠
let proxyObj = {};
proxyObj['/'] = {
    ws: false,
    target: 'http://localhost:8081',
    changeOrigin: true,
    pathRewrite: {
        '^/': ''
    }
}
module.exports = {
    devServer: {
        host: 'localhost',
        port: 8080,
        proxy: proxyObj		//代理哪個物件
    }
}

(二)登入模組
前端
①、主入口:main.js
1.建立了一個主物件Vue,然後在這個物件裡面新增了router物件(用於做頁面轉發,不同路徑對應不同的元件,也就是不同的vue檔案),以及渲染的元件render,該元件目前掛載了id為app的元件(建立的元素App對應App.Vue檔案)

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

2.也可以在main.js裡面建立全域性物件,這樣子如果要使用到的話,就不用一個個去import了,直接呼叫this.+變數名即可

Vue.prototype.postKeyValueRequest = postKeyValueRequest
Vue.prototype.postRequest = postRequest
Vue.prototype.putRequest = putRequest
Vue.prototype.getRequest = getRequest

②、渲染的模板:App.vue

<template>//網頁渲染之後<template>標籤會自動去掉
  <div id="app">  //被掛載的元件,之後渲染的東西都在這裡展現
    <router-view/>//路由導航器,用於載入頁面
  </div>
</template>

③、路由跳轉配置:/route/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Login from '../views/Login.vue'
import Home from '../views/Home.vue'

Vue.use(VueRouter) //把系統預設的route加上去
//定義自己的router。什麼路徑對應什麼元件的對映
const routes = [
    {
        path: '/',
        name: 'Login',
        component: Login
    }, {
        path: '/home',
        name: 'Home',
        component: Home
    }
]
//建立router物件,並把他匯出去
const router = new VueRouter({
    routes
})

export default router

④、登入頁 Login.vue

<template>//template標籤包起來的是登入頁面,渲染後template標籤自動去掉
  <div>
//:rules:規則應用
//ref="LoginForm" 引用物件的名稱,用於引用這個表單
//:model="LoginForm" 用於繫結資料 如下面的 LoginForm{}
    <el-form :rules="rules" ref="LoginForm" :model="LoginForm" class="LoginContainer">
      <h3 class="LoginTitle">系統登入</h3>
      <el-form-item label="使用者名稱" prop="username">
        <el-input type="text" v-model="LoginForm.username" auto-complete="off" placeholder="請輸入使用者賬戶"></el-input>
      </el-form-item>
      <el-form-item label="密碼" prop="password">
        <el-input type="password" v-model="LoginForm.password" auto-complete="off" placeholder="請輸入密碼"></el-input>
      </el-form-item>
      <el-form-item align="center">
        <el-checkbox v-model="checked" class="LoginRemeber">記住我</el-checkbox>
      </el-form-item>
      <el-form-item>
//@click="submitLogin" 繫結點選事件,也就是點選登入按鈕後呼叫submitLogin方法
        <el-button type="primary" style="width: 100%" @click="submitLogin">登入</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>

export default {
  name: "login",		//vue檔案的名字,預設給出
  data() {				//全域性的資料,主要用於初始化資料用的
    return {
      checked: true,
      LoginForm: {		//表單的初始值,如繫結的username為admin,password為123
        username: 'admin',
        password: '123'
      },
      rules: {				//欄位校驗規則
        username: [{required: true, message: '請輸入賬戶', tigger: 'blur'}],
        password: [{required: true, message: '請輸入密碼', tigger: 'blur'}]
      }
    }
  },
  methods:{			//方法區,在這裡定義需要呼叫的方法
submitLogin(){
//ref="LoginForm"用於這裡,.validate執行了校驗方法,根據LoginForm裡面的:rules裡面的規則進行校驗
      this.$refs.LoginForm.validate((valid)=>{
          if(valid){
//如果檢驗為true,就呼叫登入方法postKeyValueRequest,
該方法呼叫後端介面/doLogin,然後把LoginForm裡面的引數如username和password傳入方法中,然後返回resp,如果resp有內容,則說明登入成功(因為postKeyValueRequest方法的底層方法axios.interceptors.response.use(),當登入失敗時返回的是空值)
            console.log(this.LoginForm)
            this.postKeyValueRequest('/doLogin',this.LoginForm).then(resp=>{
              if(resp){
                window.sessionStorage.setItem("user",JSON.stringify(resp.obj));
                this.$router.replace("/home");
              }
            })
          }else{
            this.$message.error("請輸入所有欄位");
            return false;
          }
        }

      );
    }
  }
}
</script>
//樣式
<style>
    .LoginContainer {
        border-radius: 15px;
        background-clip: padding-box;
        margin: 180px auto;
        width: 350px;
        padding: 15px 35px 15px 35px;
        background: #ffffff;
        border: 1px solid #eaeaea;
        box-shadow: 0 0 25px #cac6c6;
    }
    .LoginTitle{
        margin: 20px auto 20px auto;
        text-align: center;
        color: #505458;
    }
    .LoginRemeber{

    }
</style>

⑤、API檔案:/utils/api.js
前端執行登入的方法:postKeyValueRequest()

export const postKeyValueRequest = (url, params) => {
    return axios({//相當於JS裡面的ajax    
        method: 'post',
        url: `${base}${url}`,
        data: params,
        transformRequest: [function (data) {		//對路徑和對應的索引進行轉義
            let ret = '';
            for (let i in data) {
//encodeURIComponent():因為在作用與url當作引數傳遞的時候,如引數出現空格這樣的特殊欄位,後臺只可以讀取到空格前的內容,後面內容丟失,造成資料讀取失敗,但是如果用encodeURIComponent()包裹一下,那會將這些特殊字元進行轉義,這樣後臺就可以成功讀取了,所以encodeURIComponent()用於url作為引數傳遞的場景中使用
                ret += encodeURIComponent(i) + '=' + encodeURIComponent(data[i]) + '&'
            }
            return ret;
        }],
        headers: {
//把物件頭改為表單提交的型別
            'Content-Type': 'application/x-www-form-urlencoded'
        }
    });
}

axios攔截器,相當於ajax裡面的success方法和error方法,主要是根據後端傳來的JSON串對前端的資料進行一點的修飾再將其傳出去

axios.interceptors.response.use(
success => {	//如果方法執行成功
    if (success.status && success.status == 200 && success.data.status == 500) {
        Message.error({message: success.data.msg})
        return;//此時返回空,表示如果請求失敗了,就彈出錯誤訊息後,什麼都不返回了
    }
    return success.data; //如果請求成功了  就返回資料(JSON串)
}, error => {
    if (error.response.status == 504 || error.response.status == 404) {
        Message.error({message: "伺服器被吃了!"})
    } else if (error.response.status == 403) {
        Message.error({messgae: "許可權不足"})
    } else if (error.response.status == 401) {
        Message.error({message: "尚未登入,請登入"})
    } else {
        if (error.response.data.msg) {
            Message.error({message: error.response.data.msg})
        } else {
            Message.error({message: "未知錯誤!"})
        }
    }
    return;
})

後端
①、實體類:Hr.java----->由於要整合Spring Security,因此實體類需要實現UserDtails介面,以便之後做許可權管理和username和password的校驗

public class Hr implements UserDetails {
    private Integer id;

    private String name;

    private String phone;

    private String telephone;

    private String address;

    private Boolean enabled;

    private String username;

    private String password;

    private String userface;

    private String remark;
	@Override
	//賬號是否過期
public boolean isAccountNonExpired() {
    return true;
}
//賬號是否被鎖定
@Override
public boolean isAccountNonLocked() {
    return true;
}
//密碼是否過期
@Override
public boolean isCredentialsNonExpired() {
    return true;
}
//賬號是否被禁用
@Override
public boolean isEnabled() {
    return enabled;
}
//該賬號對應的許可權
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    return null;
}

②、Mapper類:HrMapper.java

public interface HrMapper {
    int deleteByPrimaryKey(Integer id);

    int insert(Hr record);

    int insertSelective(Hr record);

    Hr selectByPrimaryKey(Integer id);

    int updateByPrimaryKeySelective(Hr record);

    int updateByPrimaryKey(Hr record);

    Hr loadUserByUsername(String username);
}

③、Service類:HrService.java---->由於Spring Security在做username和password的校驗和查詢相應的管理的時候需要查詢資料庫,因此需要實現UserDetailsService介面,實現loadUserByUsername方法,通過該方法獲取對應的使用者資訊,從而執行引數校驗和許可權管理。

@Service
public class HrService implements UserDetailsService {
    //使用者是HR,這個Service相當於User Service
    @Autowired
    private HrMapper hrMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Hr hr = hrMapper.loadUserByUsername(username);
        if(hr==null){
            throw new UsernameNotFoundException("使用者名稱不存在");
        }
        return hr;
    }
}

④、SecurityConfig.java ---->SpringSecurity起作用的話需要進行相應的配置,指定doLogin介面讓前端呼叫,由於實體類實現了UserDetails,Service類實現了UserDetailsService介面,因此後端在執行loginProcessingUrl()方法時底層會去檢驗賬戶密碼,確保登入成功還是失敗,然後將對應的東西寫成Json串寫到httpResponse中讓前端獲取(axios.interceptors.response.use())並執行相應的操作。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private HrService hrService;

    //用於加密
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception{
        auth.userDetailsService(hrService);
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()//所有請求都得被security認證
                .and()
                .formLogin()//表單登入
                .usernameParameter("username")//使用者名稱引數
                .passwordParameter("password")//密碼引數
                .loginProcessingUrl("/doLogin")//登入執行的介面,也就是按下登入按鈕的時候呼叫的介面
                .loginPage("/login")//返回的登入頁,若沒有配置,則spring security會預設配置一個登入頁,由於現在是前後端分離,因此之後這裡返回的是一個JSON串
                .successHandler(new AuthenticationSuccessHandler() {//登入成功後的處理器
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                            //httpServletRequest:請求
                            //httpServletResponse:響應
                            //authentication:儲存的登入使用者的資訊
                        httpServletResponse.setContentType("application/json;charset=utf-8");
                        PrintWriter out = httpServletResponse.getWriter();
                        Hr hr = (Hr) authentication.getPrincipal();
                        //登入成功後,構造返回資訊的JSON物件
                        //這裡把密碼設定為空的原因是防止登入後,後端返回給前端的資料中包含密碼
                        //導致密碼洩露
                        hr.setPassword(null);

                        RespBean ok = RespBean.ok("登入成功!", hr);
                        //把JSON物件寫成JSON串
                        String s = new ObjectMapper().writeValueAsString(ok);
                        out.write(s);
                        out.flush();
                        out.close();
                    }
                })
                .failureHandler(new AuthenticationFailureHandler() {//登入失敗的處理器
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                            //httpServletRequest:請求
                            //httpServletResponse:響應
                            //AuthenticationException:登入失敗後捕獲到的異常
                        httpServletResponse.setContentType("application/json;charset=utf-8");
                        PrintWriter out = httpServletResponse.getWriter();
                        RespBean error = RespBean.error("登入失敗");
                        if(e instanceof LockedException){
                            error.setMsg("賬戶被鎖定,請聯絡管理員");
                        }else if(e instanceof CredentialsExpiredException){
                            error.setMsg("密碼過期,請聯絡管理員");
                        }else if(e instanceof AccountExpiredException){
                            error.setMsg("賬戶過期,請聯絡管理員");
                        }else if(e instanceof DisabledException){
                            error.setMsg("賬戶被禁用,請聯絡管理員");
                        }else if(e instanceof BadCredentialsException){
                            error.setMsg("使用者名稱或者密碼輸入錯誤,請重新登入");
                        }
                        out.write(new ObjectMapper().writeValueAsString(error));
                        out.flush();
                        out.close();
                    }
                })
                .permitAll()
                .and()
                .logout()//退出登入
                .logoutSuccessHandler(new LogoutSuccessHandler() {//退出登入成功的處理器
                    @Override
                    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                        httpServletResponse.setContentType("application/json;charset=utf-8");
                        PrintWriter out = httpServletResponse.getWriter();
                        out.write(new ObjectMapper().writeValueAsString(RespBean.ok("登出成功")));
                        out.flush();
                        out.close();
                    }
                })
                .permitAll()
                .and()
                .csrf().disable();//關閉防止CSRF攻擊
    }
}

⑤、RespBean.java---->用於封裝JSON串的內容

public class RespBean {

    private Integer status;
    private String msg;
    private Object obj;

    public static RespBean ok(String msg){
        return new RespBean(200,msg,null);
    }

    public static RespBean ok(String msg,Object obj){
        return new RespBean(200,msg,obj);
    }

    public static RespBean error(String msg){
        return new RespBean(500,msg,null);
    }

    public static RespBean error(String msg,Object obj){
        return new RespBean(500,msg,obj);
    }

    private RespBean() {
    }

    private RespBean(Integer status, String msg, Object obj) {
        this.status = status;
        this.msg = msg;
        this.obj = obj;
    }
    Getter和Setter方法及toString方法

相關文章