1. 程式人生 > >前後端分離 SpringBoot + SpringSecurity 許可權解決方案

前後端分離 SpringBoot + SpringSecurity 許可權解決方案

一、前言

而公司這邊都是前後端分離鮮明的,前端不要接觸過多的業務邏輯,都由後端解決,基本思路是這樣的:
服務端通過 JSON字串,告訴前端使用者有沒有登入、認證,前端根據這些提示跳轉對應的登入頁、認證頁等。

二、程式碼

下面給個示例,該自上述的之前的程式碼

1.AjaxResponseBody 給前端JSON的格式

返回給前端的資料格式

package com.cun.security3.bean;

import java.io.Serializable;

public class AjaxResponseBody implements Serializable
{
private String status; private String msg; private Object result; private String jwtToken; public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public String getMsg() { return msg; } public
void setMsg(String msg) { this.msg = msg; } public Object getResult() { return result; } public void setResult(Object result) { this.result = result; } public String getJwtToken() { return jwtToken; } public void setJwtToken(String jwtToken) { this
.jwtToken = jwtToken; } }

2.AjaxAuthenticationEntryPoint 未登入

使用者沒有登入時返回給前端的資料

package com.cun.security3.config;

import com.alibaba.fastjson.JSON;
import com.cun.security3.bean.AjaxResponseBody;
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 {
        AjaxResponseBody responseBody = new AjaxResponseBody();

        responseBody.setStatus("000");
        responseBody.setMsg("Need Authorities!");

        httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
    }
}

3.AjaxAuthenticationFailureHandler 登入失敗

使用者登入失敗時返回給前端的資料

package com.cun.security3.config;

import com.alibaba.fastjson.JSON;
import com.cun.security3.bean.AjaxResponseBody;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
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 AjaxAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        AjaxResponseBody responseBody = new AjaxResponseBody();

        responseBody.setStatus("400");
        responseBody.setMsg("Login Failure!");

        httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
    }
}

4.AjaxAuthenticationSuccessHandler 登入成功

使用者登入成功時返回給前端的資料

package com.cun.security3.config;

import com.alibaba.fastjson.JSON;
import com.cun.security3.bean.AjaxResponseBody;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
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 AjaxAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        AjaxResponseBody responseBody = new AjaxResponseBody();

        responseBody.setStatus("200");
        responseBody.setMsg("Login Success!");

        httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
    }
}

5.AjaxAccessDeniedHandler 無權訪問

package com.cun.security3.config;

import com.alibaba.fastjson.JSON;
import com.cun.security3.bean.AjaxResponseBody;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
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 AjaxAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        AjaxResponseBody responseBody = new AjaxResponseBody();

        responseBody.setStatus("300");
        responseBody.setMsg("Need Authorities!");

        httpServletResponse.getWriter().write(JSON.toJSONString(responseBody));
    }
}

6.SpringSecurityConf 登入攔截全域性配置

package com.cun.security3.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;

@Configuration
public class SpringSecurityConf extends WebSecurityConfigurerAdapter {

    @Autowired
    AjaxAuthenticationEntryPoint authenticationEntryPoint;  //  未登陸時返回 JSON 格式的資料給前端(否則為 html)

    @Autowired
    AjaxAuthenticationSuccessHandler authenticationSuccessHandler;  // 登入成功返回的 JSON 格式資料給前端(否則為 html)

    @Autowired
    AjaxAuthenticationFailureHandler authenticationFailureHandler;  //  登入失敗返回的 JSON 格式資料給前端(否則為 html)

    @Autowired
    AjaxLogoutSuccessHandler  logoutSuccessHandler;  // 登出成功返回的 JSON 格式資料給前端(否則為 登入時的 html)

    @Autowired
    AjaxAccessDeniedHandler accessDeniedHandler;    // 無權訪問返回的 JSON 格式資料給前端(否則為 403 html 頁面)

    @Autowired
    SelfAuthenticationProvider provider; // 自定義安全認證

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 加入自定義的安全認證
        auth.authenticationProvider(provider);
    }

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

        http.csrf().disable()

                .httpBasic().authenticationEntryPoint(authenticationEntryPoint)

                .and()
                .authorizeRequests()

                .anyRequest()
                .authenticated()// 其他 url 需要身份認證

                .and()
                .formLogin()  //開啟登入
                .successHandler(authenticationSuccessHandler) // 登入成功
                .failureHandler(authenticationFailureHandler) // 登入失敗
                .permitAll()

                .and()
                .logout()
                .logoutSuccessHandler(logoutSuccessHandler)
                .permitAll();

        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); // 無權訪問 JSON 格式的資料

    }
}

7.SelfUserDetails 自定義 user 物件

package com.cun.security3.config;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.io.Serializable;
import java.util.Collection;
import java.util.Set;

/**
 *  ① 定義 user 物件
 */
public class SelfUserDetails implements UserDetails, Serializable {
    private String username;
    private String password;
    private Set<? extends GrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    public void setAuthorities(Set<? extends GrantedAuthority> authorities) {
        this.authorities = authorities;
    }

    @Override
    public String getPassword() { // 最重點Ⅰ
        return this.password;
    }

    @Override
    public String getUsername() { // 最重點Ⅱ
        return this.username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

8.SelfUserDetailsService 使用者認證、許可權

package com.cun.security3.config;

import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.Set;

/**
 *  ② 根據 username 獲取資料庫 user 資訊
 */
@Component
public class SelfUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        //構建使用者資訊的邏輯(取資料庫/LDAP等使用者資訊)
        SelfUserDetails userInfo = new SelfUserDetails();
        userInfo.setUsername(username); // 任意使用者名稱登入

        Md5PasswordEncoder md5PasswordEncoder = new Md5PasswordEncoder();
        String encodePassword = md5PasswordEncoder.encodePassword("123", username); // 模擬從資料庫中獲取的密碼原為 123
        userInfo.setPassword(encodePassword);

        Set authoritiesSet = new HashSet();
        GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_ADMIN"); // 模擬從資料庫中獲取使用者角色

        authoritiesSet.add(authority);
        userInfo.setAuthorities(authoritiesSet);

        return userInfo;
    }
}

9.SelfAuthenticationProvider 前端互動

package com.cun.security3.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.Set;

@Component
public class SelfAuthenticationProvider implements AuthenticationProvider {
    @Autowired
    SelfUserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String userName = (String) authentication.getPrincipal(); // 這個獲取表單輸入中返回的使用者名稱;
        String password = (String) authentication.getCredentials(); // 這個是表單中輸入的密碼;

        Md5PasswordEncoder md5PasswordEncoder = new Md5PasswordEncoder();
        String encodePwd = md5PasswordEncoder.encodePassword(password, userName);

        UserDetails userInfo = userDetailsService.loadUserByUsername(userName);

        if (!userInfo.getPassword().equals(encodePwd)) {
            throw new BadCredentialsException("使用者名稱密碼不正確,請重新登陸!");
        }

        return new UsernamePasswordAuthenticationToken(userName, password, userInfo.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return true;
    }
}

三、使用 postman 測試 ajax

1.未登入

這裡寫圖片描述

2.登入失敗

這裡寫圖片描述

3.登入成功

這裡寫圖片描述

4.登出成功

這裡寫圖片描述

四、其他

溫馨提示:session+cookie 不安全 ~
上述略有前後端分離的影子,真正前後端開發還要引入 JWT,有空再敘