SpringSecurity實現登錄和權限【真~前後端分離】

整合這個SpringSecurity花了我好幾天的時間,也讓我很頭疼。
倒不是因爲它很難,只是我搜索到的前後端分離驗證,多多少少都有些問題。
下面我就把我完整的代碼貢獻出來、避免後面的人也走坑。

1、闡述幾個問題

這裏有幾個問題需要表達一下,當然你也可以直接跳到第二步開始。

1-1、什麼是SpringSecurity

     它本質就是一個過濾器,然後在請求之前先執行這個過濾器,在這個過濾器裏面我們去進行用戶登錄,和權限進行判斷。

1-2、爲什麼我沒有用JWT來生成token

     我覺得直接使用UUID生成Token就好了,沒必要使用JWT來生成一個又長又麻煩的Token。

1-3、下面的代碼是完整的代碼嘛?

     可以說是了,本質上是基於Redis存儲數據驗證的,但是我覺得第一次學習Security的時候如果把全部都寫好加上去顯得很麻煩。Redis這塊可以根據自己定義去實現,我都標識出來了,很簡單。

1-4、如果我想獲取全部的代碼呢?

     這個也不用擔心,我正在做這個前後端分離的,登錄權限的框架,代碼是開源的。今天先分享怎麼去解決基於Security前後端分離,後面再寫一篇文章基於整個登錄權限的設計。

https://github.com/xdxTao/xdx-framework-SpringCloud

https://github.com/xdxTao/xdx-framework-vue

1-5、xxxxxx

      感覺真是坑,很多關於Security的視頻,但是都沒有講到怎麼解決前後端分離的問題。昨天晚上搞到凌晨1點才弄完。


2、代碼部分

ps:token本應該隨機生成的,但是這裏只是演示,我就在yml裏面寫死了。其實這個很好理解的。

2-1、pom依賴

 <!-- fastjson -->
<dependency>
       <groupId>com.alibaba</groupId>
       <artifactId>fastjson</artifactId>
       <version>1.2.48</version>
</dependency>
<!-- Spring Security -->
<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-security</artifactId>
       <version>2.0.0.RELEASE</version>
</dependency>

2-2、AjaxResult

ps:這個就是一個結果集統一封裝,相信大部分人都明白。

/**
 * 封裝返回結果集
 *
 * @author 小道仙
 * @date 2020年2月17日
 */
@Data
@Accessors(chain = true)
public class AjaxResult<T> {
    /**
     * 返回狀態碼
     */
    private Integer code;

    /**
     * 返回的數據
     */
    private T data;

    /**
     * 總條數
     */
    private Integer total;

    /**
     * 成功與否
     */
    private Boolean success;

    /**
     * 消息提示
     */
    private String msg;

    /**
     * 錯誤描述
     */
    private String errDesc;

    /**
     * 用戶token
     */
    private String xdxToken;

    public AjaxResult() {
    }

    /**
     * 操作失敗
     * @param errDesc 錯誤信息
     *
     * @author 小道仙
     * @date 2020年2月17日
     */
    public static AjaxResult<?> failure(String errDesc) {
        return new AjaxResult<>().setErrDesc(errDesc).setSuccess(false);
    }

    /**
     * 操作成功
     * @param msg  返回消息
     * @param total 總條數
     * @param data 返回的數據
     *
     * @author 小道仙
     * @date 2020年2月17日
     */
    public static <T> AjaxResult<T> success(String msg,Integer total,T data){
        AjaxResult<T> result = new AjaxResult<>();
        result.setSuccess(true)
                .setTotal(total)
                .setMsg(msg);
        return result;
    }

    /**
     * 操作成功
     * @param total 總條數
     * @param data 返回的數據
     *
     * @author 小道仙
     * @date 2020年2月17日
     */
    public static <T> AjaxResult<T> success(T data,Integer total){
        AjaxResult<T> result = new AjaxResult<>();
        result.setSuccess(true)
                .setTotal(total)
                .setMsg("操作成功")
                .setData(data);
        return result;
    }

    /**
     * 操作成功
     * @param data 返回的數據
     *
     * @author 小道仙
     * @date 2020年2月22日
     */
    public static <T> AjaxResult<T> success(T data){
        AjaxResult<T> result = new AjaxResult<>();
        result.setSuccess(true)
                .setMsg("操作成功")
                .setData(data);
        return result;
    }

    /**
     * 操作成功
     * @param msg  返回消息
     *
     * @author 小道仙
     * @date 2020年2月17日
     */
    public static <T> AjaxResult<T> success(String msg){
        return success(msg,0,null);
    }

    /**
     * 操作成功
     * @param msg  返回消息
     * @param total 總條數
     *
     * @author 小道仙
     * @date 2020年2月17日
     */
    public static <T> AjaxResult<T> success(String msg,Integer total){
        return success(msg,total,null);
    }

    /**
     * 操作成功
     *
     * @author 小道仙
     * @date 2020年2月17日
     */
    public static <T> AjaxResult<T> success(){
        return success("操作成功",0,null);
    }
}

2-3、SecurityConfig

在這裏插入圖片描述

2-3-1:SecurityConfig
import com.xdx97.framework.config.security.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;


@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter  {

    /**
     *  未登陸時返回 JSON 格式的數據給前端(否則爲 html)
     */
    @Autowired
    AjaxAuthenticationEntryPoint authenticationEntryPoint;

    /**
     * 註銷成功返回的 JSON 格式數據給前端(否則爲 登錄時的 html)
     */
    @Autowired
    AjaxLogoutSuccessHandler logoutSuccessHandler;


    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // 去掉 CSRF
        http.csrf().disable()
                .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
                .and()
                // 基於Token 不需要Session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                // 登錄處理
                .and()
                .formLogin()
                .loginProcessingUrl("/user/login")
                .permitAll()

                //  登錄和權限控制
                .and()
                .authorizeRequests()
                .anyRequest()
                // RBAC 動態 url 認證
                .access("@rbacauthorityservice.hasPermission(request,authentication)")



                //註銷處理
                .and()
                .logout()//默認註銷行爲爲logout
                .logoutUrl("/user/loginOut")
                .logoutSuccessHandler(logoutSuccessHandler)
                .permitAll();
    }
}

2-3-2:AjaxAuthenticationEntryPoint
import com.alibaba.fastjson.JSON;
import com.xdx97.framework.common.AjaxResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 用戶沒有登錄時返回給前端的數據
 */
@Component
public class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {

        AjaxResult ajaxResult = new AjaxResult();

        String flagName = httpServletRequest.getAttribute("flagName").toString();

        if (flagName.equals("未登錄")){
            ajaxResult.setCode(888)
                    .setErrDesc("未登錄,請登錄!");
        } else if (flagName.equals("權限不足")){
            ajaxResult.setCode(999)
                    .setErrDesc("權限不足!");
        } else{
            ajaxResult.setCode(000)
                    .setErrDesc("系統異常!");
        };

        httpServletResponse.setContentType("text/html;charset=utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(ajaxResult));
    }
}

2-3-2:AjaxLogoutSuccessHandler
import com.alibaba.fastjson.JSON;
import com.xdx97.framework.common.AjaxResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 退出登錄
 */
@Component
public class AjaxLogoutSuccessHandler implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {

        // 從這裏拿到token 然後把這個token註銷
        String token = httpServletRequest.getHeader("xdxToken");

        // 去redis刪除token

        System.out.println("123123213");


        AjaxResult ajaxResult = new AjaxResult();
        ajaxResult.setCode(100)
                .setErrDesc("退出成功!");

        httpServletResponse.setContentType("text/html;charset=utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(ajaxResult));
    }
}

2-3-2:RbacAuthorityService
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;

@Component("rbacauthorityservice")
public class RbacAuthorityService {

    @Autowired
    private Environment env;

    public boolean hasPermission(HttpServletRequest request,Authentication authentication) {

        // 獲取當前請求的URI
        String requestURI = request.getRequestURI();

        // 放開登錄url
        if (requestURI.equals("/user/login")){
            return true;
        }

        // 登錄判斷
        String token = request.getHeader("xdxToken");
        if (token == null || !token.equals(env.getProperty("xdxToken"))){
            request.setAttribute("flagName","未登錄");
            return false;
        }

        // 權限判斷
        // 利用token去Redis取出當前角色的權限,這裏就直接寫死了
        List<String> roles = new ArrayList<>();
        roles.add("/user/list");
        roles.add("/user/menu");
        roles.add("/user/loginOut");

        if (!roles.contains(requestURI)){
            request.setAttribute("flagName","權限不足");
            return false;
        }
        return true;
    }
}

2-4:另外我們需要三個接口,一個登錄(/user/login),一個測試(/user/list),一個測試(/authority/menu/list)

ps:兩個測試接口你不必在意裏面的實現,直接打印一句話就也行了,主要是看能不能訪問到。

2-4-1:登錄(/user/login)

controller

    @GetMapping("/user/login")
    public AjaxResult<?> login(@RequestParam String userName, @RequestParam String userPassword){
        User user = new User();
        user.setUserName(userName).setUserPassword(userPassword);
        return userServiceImpl.login(user);
    }

service:這個Environment 是用來獲取yml文件裏面的值的

    @Autowired
    private Environment env;

    @Override
    public AjaxResult<?> login(User user) {
        AjaxResult ajaxResult = new AjaxResult();
        /**
         * 1、獲取到了用戶名和密碼去進行判斷是否正確
         * 2、如果驗證不成功,這裏我默認用戶名密碼必須等於 admin admin
         */
        if (! ("admin".equals(user.getUserName()) && "admin".equals(user.getUserPassword()))){
            ajaxResult.setCode(222).setErrDesc("用戶名或密碼錯誤!");
            return ajaxResult;
        }

        // 3、如果驗證成功了,就返回 token,當然了我們現需要把token存入Redis這裏就省略了
        ajaxResult.setCode(200).setMsg("登錄成功!").setXdxToken(env.getProperty("xdxToken"));

        return ajaxResult;
    }

2-5、yml

在這裏插入圖片描述


2-6:總結

    RbacAuthorityService是對所有請求進行攔截的,當被攔截到時,就會進入AjaxAuthenticationEntryPoint

    當我們要退出的時候訪問 (/user/loginOut) 這時會進入 AjaxLogoutSuccessHandler

    當然了你可以根據自己的需求繼續加入Filter


3、演示

3-1:登錄/退出

ps:我也不知道爲啥登錄只能用get請求,還沒研究爲什麼,有興趣可以自行研究。

在這裏插入圖片描述
在這裏插入圖片描述


3-2:測試其它的接口

在這裏插入圖片描述
在這裏插入圖片描述



如果對你有幫助,或者對我感覺興趣的話,可以關注我的公衆號支持一下我噢
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章