SpringBoot2.1版本的我的應用開發框架 - 整合vue實現先後端分離

本篇做爲SpringBoot2.1版本的我的開發框架 子章節,請先閱讀SpringBoot2.1版本的我的開發框架再次閱讀本篇文章html

後端項目地址:SpringBoot2.1版本的我的應用開發框架前端

前端項目地址:ywh-vue-adminvue

感謝PanJiaChen大神給咱們建立了這麼好的vue後端管理模板,大神有一系列的教程,在預覽地址中有系列文章的地址,還有項目github的地址,感受大神就是帥氣。java

下載PanJiaChen大神的vue-admin-template,這個是大神推薦的二次開發的模板,vue-element-admin大神但願是一個集成方案,當咱們須要什麼再去拿什麼。redis

這裏我不對項目結構作介紹,在大神的系列文章中都有介紹,當咱們把項目下載好之後,咱們嘗試的在本地跑起來,肯定沒有錯誤之後咱們再進行下一步。spring

啓動初始vue-admin-template項目

在下載好的項目中運行cmd,先下載項目所須要的依賴後再啓動數據庫

npm install
。。。。
npm run dev
複製代碼

效果圖,如今登錄的仍是默認的用戶,咱們要實現的功能是:前端與後端作交互,並在數據庫中查詢用戶時候否是有權限登錄。

在這裏插入圖片描述

跑起來後登錄的界面如上圖,沒有預覽的功能多,因此之後咱們按照咱們本身想要的需求一一加進去。

後端項目

咱們想要後端與前端交互起來,其實仍是須要修改挺多地方的,這裏先介紹修改後端,在先後端分離的項目中多數用Token來作請求的認證,我也是實現了jwt和SpringSecurity來保護API,他們倆在我理解來看是沒有直接關係的,而是合做的關係,由SpringSecurity來決定什麼請求能夠訪問咱們服務器,能夠訪問的請求再由jwt來判斷是否攜帶Token,沒有攜帶的不予經過,再加上網上不少都是經過這種模式來實現的,參考的資料也比較多。

推薦: 重拾後端之Spring Boot(四):使用JWT和Spring Security保護REST API

jwt的建立

在security模塊中的application-security.yml文件中添加如下內容,jwt的加密字符串是一個提早寫好的,這裏就至關於配置了三個常量,並無什麼特別的,以後會在類中加載,若是閒麻煩,能夠直接在類中定義常量便可。

jwt:
 header: token   #jwt的請求頭
 secret: eyJleHAiOjE1NDMyMDUyODUsInN1YiI6ImFkbWluIiwiY3Jl   #jwt的加密字符串
 expiration: 3600000   #jwt token有效時間(毫秒)一個小時
複製代碼

在ywh-starter-security模塊的utils包中建立JwtTokenUtil工具類,若是想看詳細的代碼,能夠前往個人GitHub查看詳細代碼。

package com.ywh.security.utils;

/** * CreateTime: 2019-01-22 10:27 * ClassName: JwtTokenUtil * Package: com.ywh.security.utils * Describe: * jwt的工具類 * * @author YWH */
@Data
@Component
@ConfigurationProperties(prefix = "jwt")
public class JwtTokenUtil {

    private String secret;

    private Long expiration;

    private String header;


    /** * 從數據聲明生成令牌 * * @param claims 數據聲明 * @return 令牌 */
    private String generateToken(Map<String, Object> claims) {
        Date expirationDate = new Date(System.currentTimeMillis() + expiration);
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();
    }

    /** * 生成令牌 * @return 令牌 */
    public String generateToken(String userName) {
        Map<String, Object> claims = new HashMap<>(2);
        claims.put("sub", userName);
        claims.put("created", new Date());
        return generateToken(claims);
    }
	。。。。。。。。。。。。中間省略了代碼

}

複製代碼

在咱們前端向後端請求時,咱們要每一次的判斷是否攜帶了token,這個任務咱們就交給攔截器來執行,建立JwtAuthenticationTokenFilter攔截器

package com.ywh.security.filter;

/** * CreateTime: 2019-01-29 18:15 * ClassName: JwtAuthenticationTokenFilter * Package: com.ywh.security.filter * Describe: * spring的攔截器 * * @author YWH */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    private final static Logger log = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);

    private JwtTokenUtil jwtTokenUtil;
    private UserDetailsService userDetailsService;

    @Autowired
    public JwtAuthenticationTokenFilter(JwtTokenUtil jwtTokenUtil, UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
        this.jwtTokenUtil = jwtTokenUtil;
    }

    /** * 該攔截器主要的功能是,攔截請求後,判斷是否攜帶token,若是未攜帶token則不予經過。 * @param httpServletRequest http請求 * @param httpServletResponse http響應 * @param filterChain 攔截器 * @throws ServletException 異常信息 * @throws IOException 異常信息 */
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {

        // 獲取request中jwt token
        String authHeader = httpServletRequest.getHeader(jwtTokenUtil.getHeader());
        // 驗證token是否存在
        if(StringUtils.isNotEmpty(authHeader)){
            //根據token獲取用戶名
            String userName = jwtTokenUtil.getUsernameFromToken(authHeader);
            if(userName != null && SecurityContextHolder.getContext().getAuthentication() == null){
                // 經過用戶名 獲取用戶的信息
                UserDetails userDetails = userDetailsService.loadUserByUsername(userName);
                // 驗證token和用戶信息是否匹配
                if(jwtTokenUtil.validateToken(authHeader,userDetails)){
                    // 而後構造UsernamePasswordAuthenticationToken對象
                    // 最後綁定到當前request中,在後面的請求中就能夠獲取用戶信息
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        filterChain.doFilter(httpServletRequest, httpServletResponse);

    }
}

複製代碼

攔截器寫好之後,咱們須要修改SecurityConfigurer類中configure(HttpSecurity httpSecurity)方法,這個類在我上兩篇文章中都有介紹,如下代碼中我寫了跨域請求的後端實現。後面咱們就不用在前端實現跨域請求的設置了,不過我也會把前端如何實現跨域寫出來的。

/** * 配置如何經過攔截器保護咱們的請求,哪些能經過哪些不能經過,容許對特定的http請求基於安全考慮進行配置 * @param httpSecurity http * @throws Exception 異常 */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {

        httpSecurity
                // 暫時禁用csrc不然沒法提交
                .csrf().disable()
                // session管理
                .sessionManagement()
                // 咱們使用SessionCreationPolicy.STATELESS無狀態的Session機制(即Spring不使用HTTPSession),對於全部的請求都作權限校驗,
                // 這樣Spring Security的攔截器會判斷全部請求的Header上有沒有」X-Auth-Token」。
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                // 設置最多一個用戶登陸,若是第二個用戶登錄則第一用戶被踢出,並跳轉到登錄頁面
                .maximumSessions(1).expiredUrl("/login.html");
        httpSecurity
                // 開始認證
                .authorizeRequests()
                // 對靜態文件和登錄頁面放行
                .antMatchers("/static/**").permitAll()
                .antMatchers("/auth/**").permitAll()
                .antMatchers("/login.html").permitAll()
                // 其餘請求須要認證登錄
                .anyRequest().authenticated();

        // 注入咱們剛纔寫好的 jwt過濾器,添加在UsernamePasswordAuthenticationFilter過濾器以前
        httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        // 這塊是配置跨域請求的
         ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
        // 讓Spring security放行全部preflight request
        registry.requestMatchers(CorsUtils::isPreFlightRequest).permitAll();
 }
 
 /** * 這塊是配置跨域請求的 * @return Cors過濾器 */
    @Bean
    public CorsFilter corsFilter() {
        final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration cors = new CorsConfiguration();
        cors.setAllowCredentials(true);
        cors.addAllowedOrigin("*");
        cors.addAllowedHeader("*");
        cors.addAllowedMethod("*");
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", cors);
        return new CorsFilter(urlBasedCorsConfigurationSource);
    }
複製代碼

能夠看到上面代碼中,我把咱們實現的攔截器放到了SpringSecurity的攔截器鏈中去了,這就使他們倆有了合做的關係,接下來就是建立咱們的servicecontroller,實現最基本的登錄和退出,用戶登錄後返回一個Token,前端存在本地緩存(localStorage)或者sessionStorage中,以供以後的請求使用。

package com.ywh.security.service.impl;

/** * CreateTime: 2019-01-25 * ClassName: SysUserServiceImpl * Package: com.ywh.security.service.impl * Describe: * 業務邏輯接口的實現類 * @author YWH */
@Service
public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntity> implements SysUserService {

    private static final Logger log = LoggerFactory.getLogger(SysUserServiceImpl.class);

    @Autowired
    private SysUserDao dao;

    @Autowired
    private AuthenticationManager authenticate;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    /** * 獲取用戶詳細信息 * @param username 用戶名 * @return 實體類 */
    @Override
    public SysUserEntity findUserInfo(String username) {
        return dao.selectByUserName(username);
    }

    /** * 用戶登錄 * @param username 用戶名 * @param password 密碼 * @return 登錄成功 返回token */
    @Override
    public String login(String username, String password) throws AuthenticationException {
        // 內部登陸請求
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        // 驗證是否有權限
        Authentication auth = authenticate.authenticate(authRequest);
        log.debug("===============權限============" + auth);
        SecurityContextHolder.getContext().setAuthentication(auth);

        return jwtTokenUtil.generateToken(username);
    }
}

複製代碼

Controller的實現,由於都是最簡單的實現,能夠根據本身的需求修改,後期也能夠再根據本身的想法加相應的實現便可。

package com.ywh.security.controller;

/** * CreateTime: 2019-01-28 16:06 * ClassName: AuthController * Package: com.ywh.security.controller * Describe: * 權限控制器 * * @author YWH */
@RestController
@RequestMapping("auth")
public class AuthController {


    private static final Logger LOG = LoggerFactory.getLogger(AuthController.class);


    @Autowired
    private SysUserService sysUserService;


    /** * 登錄 * @param map 接收體 * @return 返回token */
    @PostMapping("login")
    public Result login(@RequestBody Map<String, String> map){
        try {
            String token = sysUserService.login(map.get("username"), map.get("password"));
            return Result.successJson(token);
        }catch (AuthenticationException ex){
            LOG.error("登錄失敗",ex);
            return Result.errorJson(BaseEnum.PASSWORD_ERROR.getMsg(),BaseEnum.PASSWORD_ERROR.getIndex());
        }

    }

    /** * 用戶詳情 * @return 用戶詳細信息 */
    @Cacheable(value = "userInfo")
    @GetMapping("userInfo")
    public Result userInfo(){
        Object authentication = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if(authentication instanceof SecurityUserDetails){
            return Result.successJson(sysUserService.findUserInfo(((SecurityUserDetails) authentication).getUsername()));
        }
        return Result.errorJson(BaseEnum.LOGIN_AGIN.getMsg(),BaseEnum.LOGIN_AGIN.getIndex());
    }

    @PostMapping("logOut")
    public Result logOut(){
        return Result.successJson("退出成功,由於token自己是無狀態,若是經過redis來控制token的生存週期,則變成了有狀態,因此暫時沒有好的解決辦法。");
    }

}

複製代碼

到此就結束了後端項目的修改,我覺的最重要的是要明白它們是怎麼樣的工做流程,知道流程後咱們就好理解不少,一步一步往下寫就能夠了,碰到不會的多Google多百度便可。

jwt和SpringSecurity的流程總結:

  • 先要有一個能夠生成Token的工具類。
  • 實現jwt的攔截器,判斷每一次請求是否攜帶Token。
  • 修改Security的配置類,使jwt和Security聯繫起來,有合做關係。
  • 建立service實現最簡單的登錄以及查詢用戶等操做。
  • 建立Controller,提供前端所要的接口。

前端項目

在上面咱們把前端項目vue-elment-template跑起來後,須要修改挺多地方的,比較雜,我也是遇到一個錯誤修改一個地方。

在後端項目中我已經實現了後端跨域的方法,可是前端也是能夠實現跨域請求的,二者選擇哪一個均可以。

  • vue-element-template跨域問題,須要把src/utils/request.js中的baseURL的地址去掉,而後在配置/config/index.js中的proxyTable 解決跨域問題。
proxyTable: {
  '/core': {
    target: 'http://192.168.0.117:8082', // 接口的域名
    // secure: false, // 若是是https接口,須要配置這個參數
    changeOrigin: true, // 若是接口跨域,須要進行這個參數配置
    pathRewrite: {
      '^/core': '/core'
    }
  }
},
複製代碼
  • config\dev.env.js和config\prod.env.js 修改訪問根路徑
'use strict'
module.exports = {
  NODE_ENV: '"production"',
  BASE_API: '"http://localhost:8082/core/"',
}
複製代碼
  • src\api\login.js 解決訪問路徑問題,因爲咱們controller中的路徑使auth/**,因此要修改爲咱們本身的路徑,我只寫一個示例,剩下的按着修改。
export function login(username, password) {
  return request({
    url: '/auth/login',
    method: 'post',
    data: {
      username,
      password
    }
  })
}
複製代碼
  • src\utils\request.js 修改token名字,這個就是修改Header頭中攜帶的Token名字,這個是後端決定的,咱們在前面的yml文件中定義的什麼這裏就寫什麼,還有就是狀態碼等。

在這裏插入圖片描述

在這裏插入圖片描述

  • src\store\modules\user.js 修改登錄等問題,在這裏咱們要修改的比較多一點,語言描述也不太好描述,我就簡略劫了兩張圖,若是遇到錯誤本身解決掉正好多熟悉一下。

在這裏插入圖片描述

在這裏插入圖片描述

以上差很少就是我在前端遇到的大問題,頗有不少小問題,就不一一貼了,設置了以上後,能夠先試一試能不能跑起來,若是不行,能夠對比我在GitHub的代碼。

效果圖

當咱們再次登錄時,能夠看到咱們已是在數據庫中查詢用戶信息而且登錄了。

用戶登陸成功

若是再以默認的admin登錄則顯示用戶名或密碼錯誤

用戶登陸失敗
相關文章
相關標籤/搜索