SpringSecurity配置,簡單梳理


生活加油:摘一句子:

“我希望自己能寫這樣的詩。我希望自己也是一顆星星。如果我會發光,就不必害怕黑暗。如果我自己是那么美好,那么一切恐懼就可以煙消雲散。於是我開始存下了一點希望—如果我能做到,那么我就戰勝了寂寞的命運。”

                                                                                                                                                -----------------------------王小波《我在荒島上迎接黎明》
————————————————
嗯,剛學的時候,一大堆,明白是配置了啥,不知道為啥那么配,嗯,今天把配置思想和小伙伴分享,是很簡單的那種,不足之處請留言.

SpringSecurity的配置基於WebSecurityConfigurerAdapter的實現類,我們這里主要講基本配置,即configure(HttpSecurity http)方法的配置,其實大都有默認值,我們可以直接用默認值,也可以自己設置.

私以為簡單配置三點:

  • 認證(SpringSecurity在第一次登錄做認證,這時候只進行認證,不進行授權(鑒權處理))
    • 表單認證:涉及到的配置包括前端登錄請求的url,登錄失敗,登錄成功返回的頁面url,已及表單返回字段的用戶名密碼K的設置,還有登陸成功回調處理登錄失敗回調處理.這里要注意登錄數據是通過key/value的形式來傳遞.
    • 自定義認證,(驗證碼):自定義驗證采用,在自定義過濾器的方式,在最開始插入一個過濾器鏈,需要自定義異常,自定義失敗登錄失敗處理器,注意這里的失敗處理器要和表單登錄使用同一個失敗處理器.
  • 授權(基於RBAC權限控制):
    • 授權這樣分析基於RBAC的權限控制,即用戶分配角色,角色分配權限.需要配置根據訪問路徑得到需要的角色的處理類(FilterlnvocationSecurityrMetadataSource),根據返回角色進行鑒權處理類(AccessDecisionManager) , 同時判斷是否登錄,還需要配置認證失敗的處理器.
  • 注銷登錄
    • 注銷登錄默認的url為"/logout".需要配置,注銷登錄處理器

配置圖:

 SpringSecurity簡單配置圖:

 下面就上面講的代碼分析

一.認證:

1.configure(HttpSecurity http)方法的配置:

package com.liruilong.hros.config;
 
import com.fasterxml.jackson.databind.ObjectMapper;
import com.liruilong.hros.filter.VerifyCodeFilter;
import com.liruilong.hros.model.Hr;
import com.liruilong.hros.model.RespBean;
import com.liruilong.hros.service.HrService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.ObjectPostProcessor;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
 
/**
 * @Description :
 * @Author: Liruilong
 * @Date: 2019/12/18 19:11
 */
 
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    HrService hrService;
    @Autowired
    CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;
    @Autowired
    CustomUrlDecisionManager customUrlDecisionManager;
     @Autowired
    VerifyCodeFilter verifyCodeFilter ;
    @Autowired
    MyAuthenticationFailureHandler myAuthenticationFailureHandler;
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class)
                .authorizeRequests()
                //.anyRequest().authenticated()
                //所有請求的都會經過這進行鑒權處理。返回當前請求需要的角色。
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
                        object.setAccessDecisionManager(customUrlDecisionManager);
                        return object;
                    }
                })
                .and().formLogin().usernameParameter("username").passwordParameter("password")
                //設置登錄請求的url路徑
                .loginProcessingUrl("/doLogin")
                /*需要身份驗證時,將瀏覽器重定向到/ login
                我們負責在請求/ login時呈現登錄頁面
                當身份驗證嘗試失敗時,將瀏覽器重定向到/ login?error(因為我們沒有另外指定)
                當請求/ login?error時,我們負責呈現失敗頁面
                成功注銷后,將瀏覽器重定向到/ login?logout(因為我們沒有另外指定)
                我們負責在請求/ login?logout時呈現注銷確認頁面*/
                .loginPage("/login")
                //登錄成功回調
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                        httpServletResponse.setContentType("application/json;charset=utf-8");
                        PrintWriter out = httpServletResponse.getWriter();
                        Hr hr = (Hr) authentication.getPrincipal();
                        //密碼不回傳
                        hr.setPassword(null);
                        RespBean ok = RespBean.ok("登錄成功!", hr);
                        //將hr轉化為Sting
                        String s = new ObjectMapper().writeValueAsString(ok);
                        out.write(s);
                        out.flush();
                        out.close();
                    }
                })
                //登失敗回調
                .failureHandler(myAuthenticationFailureHandler)
                //相關的接口直接返回
                .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().exceptionHandling()
                //沒有認證時,在這里處理結果,不要重定向
                .authenticationEntryPoint(
                        //myAuthenticationEntryPoint;
                        new AuthenticationEntryPoint() {
                    @Override
                    public void commence(HttpServletRequest req, HttpServletResponse resp, AuthenticationException authException) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        resp.setStatus(401);
                        PrintWriter out = resp.getWriter();
                        RespBean respBean = RespBean.error("訪問失敗!");
                        if (authException instanceof InsufficientAuthenticationException) {
                            respBean.setMsg("請求失敗,請聯系管理員!");
                        }
                        out.write(new ObjectMapper().writeValueAsString(respBean));
                        out.flush();
                        out.close();
                    }
                });
    }
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(hrService);
    }
    /**
     * @Author Liruilong
     * @Description  放行的請求路徑
     * @Date 19:25 2020/2/7
     * @Param [web]
     * @return void
     **/
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/auth/code","/login","/css/**","/js/**", "/index.html", "/img/**", "/fonts/**","/favicon.ico");
    }
}
 
 

 

2.失敗處理器定義:

package com.liruilong.hros.config;
 
import com.fasterxml.jackson.databind.ObjectMapper;
import com.liruilong.hros.Exception.ValidateCodeException;
import com.liruilong.hros.model.RespBean;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.*;
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;
import java.io.PrintWriter;
 
/**
 * @Description :
 * @Author: Liruilong
 * @Date: 2020/2/11 23:08
 */
@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=utf-8");
        PrintWriter out = httpServletResponse.getWriter();
        RespBean respBean = RespBean.error(e.getMessage());
           // 驗證碼自定義異常的處理
        if (e instanceof ValidateCodeException){
            respBean.setMsg(e.getMessage());
            //Security內置的異常處理
        }else if (e instanceof LockedException) {
            respBean.setMsg("賬戶被鎖定請聯系管理員!");
        } else if (e instanceof CredentialsExpiredException) {
            respBean.setMsg("密碼過期請聯系管理員!");
        } else if (e instanceof AccountExpiredException) {
            respBean.setMsg("賬戶過期請聯系管理員!");
        } else if (e instanceof DisabledException) {
            respBean.setMsg("賬戶被禁用請聯系管理員!");
        } else if (e instanceof BadCredentialsException) {
            respBean.setMsg("用戶名密碼輸入錯誤,請重新輸入!");
        }
        //將hr轉化為Sting
        out.write(new ObjectMapper().writeValueAsString(respBean));
        out.flush();
        out.close();
    }
    @Bean
    public  MyAuthenticationFailureHandler getMyAuthenticationFailureHandler(){
        return new MyAuthenticationFailureHandler();
    }
}

 

3.前端將JSON數據轉換為K-V形式:

//post請求的封裝K-v形式
let base = '';
export const postKeyValueRequest = (url, params) => {
    return axios({
        method: 'post',
        url: `${base}${url}`,
        data: params,
        transformRequest: [function (data) {
            let ret = ''
            for (let it in data) {
                ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
            }

            return ret
        }],
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
        }
    });
}

 4.自定義過濾器:

package com.liruilong.hros.filter;
 
 
import com.liruilong.hros.Exception.ValidateCodeException;
import com.liruilong.hros.config.MyAuthenticationFailureHandler;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
 
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
 
/**
 * @Description :
 * @Author: Liruilong
 * @Date: 2020/2/7 19:39
 */
@Component
public class VerifyCodeFilter extends OncePerRequestFilter {
 
    @Bean
    public VerifyCodeFilter getVerifyCodeFilter() {
        return new VerifyCodeFilter();
    }
    @Autowired
    MyAuthenticationFailureHandler myAuthenticationFailureHandler;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
 
        if (StringUtils.equals("/doLogin", request.getRequestURI())
                && StringUtils.equalsIgnoreCase(request.getMethod(), "post")) {
            // 1. 進行驗證碼的校驗
            try {
            String requestCaptcha = request.getParameter("code");
                if (requestCaptcha == null) {
                    throw new ValidateCodeException("驗證碼不存在");
                }
            String code = (String) request.getSession().getAttribute("yanzhengma");
                if (StringUtils.isBlank(code)) {
                    throw new ValidateCodeException("驗證碼過期!");
                }
            code = code.equals("0.0") ? "0" : code;
            logger.info("開始校驗驗證碼,生成的驗證碼為:" + code + " ,輸入的驗證碼為:" + requestCaptcha);
                if (!StringUtils.equals(code, requestCaptcha)) {
                    throw new ValidateCodeException("驗證碼不匹配");
                }
            } catch (AuthenticationException e) {
                // 2. 捕獲步驟1中校驗出現異常,交給失敗處理類進行進行處理
                myAuthenticationFailureHandler.onAuthenticationFailure(request, response, e);
            } finally {
                filterChain.doFilter(request, response);
            }
        } else {
            filterChain.doFilter(request, response);
        }
 
 
    }
 
}

 5.自定義異常:

package com.liruilong.hros.Exception;
 
 
import org.springframework.security.core.AuthenticationException;
 
 
/**
 * @Description :
 * @Author: Liruilong
 * @Date: 2020/2/8 7:24
 */
 
public class ValidateCodeException extends AuthenticationException  {
 
    public ValidateCodeException(String msg) {
        super(msg);
    }
 
 
}

 二授權:

RBAC模型實體圖

 1.開發者自定義 FilterlnvocationSecurityrMetadataSource

package com.liruilong.hros.config;
 
import com.liruilong.hros.mapper.MenuMapper;
import com.liruilong.hros.model.Menu;
import com.liruilong.hros.model.Role;
import com.liruilong.hros.service.MenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
 
import java.util.Collection;
import java.util.List;
 
/**
 * @Description : 權限處理,根據請求,分析需要的角色,該類的主要功能就是通過當前的請求地址,獲取該地址需要的用戶角色
 * @Author: Liruilong
 * @Date: 2019/12/24 12:17
 */
@Component
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    @Autowired
    MenuService menuService;
    //路徑比較工具
    AntPathMatcher antPathMatcher = new AntPathMatcher();
    /**
     * @return java.util.Collection<org.springframework.security.access.ConfigAttribute>
     * @Author Liruilong
     * @Description 當前請求需要的角色
     * @Date 18:13 2019/12/24
     * @Param [object]
     **/
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        //獲取當前請求路徑
        String requestUrl = ((FilterInvocation) object).getRequestUrl();
        //獲取所有的菜單url路徑
        List<Menu> menus = menuService.getAllMenusWithRole();
        for (Menu menu : menus) {
            if (antPathMatcher.match(menu.getUrl(), requestUrl)) {
                //擁有當前菜單權限的角色
                List<Role> roles = menu.getRoles();
                String[] strings = new String[roles.size()];
                for (int i = 0; i < roles.size(); i++) {
                    strings[i] = roles.get(i).getName();
                }
                return SecurityConfig.createList(strings);
            }
        }
        // 沒匹配上的資源都是登錄
        return SecurityConfig.createList("ROLE_LOGIN");
    }
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }
 
    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

2.自定義 AccessDecisionManager

package com.liruilong.hros.config;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
 
import java.util.Collection;
 
/**
 * @Description : 判斷當前用戶是否具備菜單訪問,當一個請求走完 FilterlnvocationSecurityMetadataSource 中的 getAttributes 方法后,接下來就會 來到 AccessDecisionManager 類中進行角色信息的比對
 * @Author: Liruilong
 * @Date: 2019/12/24 19:12
 */
@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
 
    /**
     * @return void
     * @Author Liruilong
     * @Description decide 方法有三個參數, 第一個參數包含當前登錄用戶的信息;
     * 第二個參數則是一個 Filterlnvocation 對 象 ,可以 獲 取當前請求對 象等;
     * 第 三個參 數就是 FilterlnvocationSecurityMetadataSource 中的 getAttributes 方法的返回值, 即當前請求 URL 所 需要的角色。
     * @Date 18:28 2020/2/13
     * @Param [authentication, object, configAttributes]
     **/
 
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
            throws AccessDeniedException, InsufficientAuthenticationException {
        for (ConfigAttribute configAttribute : configAttributes) {
            String needRole = configAttribute.getAttribute();
            if ("ROLE_LOGIN".equals(needRole)) {
                //判斷用戶是否登錄
                if (authentication instanceof AnonymousAuthenticationToken) {
                    throw new AccessDeniedException("尚未登錄,請登錄!");
                } else {
                    return;
                }
            }
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                if (authority.getAuthority().equals(needRole)) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("權限不足,請聯系管理員!");
    }
 
    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }
 
    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}
// authorities.stream().anyMatch((authority) ->authority.getAuthority().equals(attribute));

 

 

 

 

 

 

 RBAC模型分析

3. 當然還需要用戶類去實現UserDatails接口

package com.liruilong.hros.model;
 
 
import com.fasterxml.jackson.annotation.JacksonInject;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreType;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
 
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
 
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;
 
    private List<Role> roles;
 
    public Integer getId() {
        return id;
    }
 
    public void setId(Integer id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name == null ? null : name.trim();
    }
 
    public String getPhone() {
        return phone;
    }
 
    public void setPhone(String phone) {
        this.phone = phone == null ? null : phone.trim();
    }
 
    public String getTelephone() {
        return telephone;
    }
 
    public void setTelephone(String telephone) {
        this.telephone = telephone == null ? null : telephone.trim();
    }
 
    public String getAddress() {
        return address;
    }
 
    public void setAddress(String address) {
        this.address = address == null ? null : address.trim();
    }
 
 
 
    public void setEnabled(Boolean enabled) {
        this.enabled = enabled;
    }
 
    @Override
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username == null ? null : username.trim();
    }
 
    @Override
    public String getPassword() {
        return password;
    }
 
    public void setPassword(String password) {
        this.password = password == null ? null : password.trim();
    }
 
    public String getUserface() {
        return userface;
    }
 
    public void setUserface(String userface) {
        this.userface = userface == null ? null : userface.trim();
    }
 
    public String getRemark() {
        return remark;
    }
 
    public void setRemark(String remark) {
        this.remark = remark == null ? null : remark.trim();
    }
 
    @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
    @JsonIgnore
    public Collection<? extends GrantedAuthority> getAuthorities() {
      List<SimpleGrantedAuthority> authorities = new ArrayList<>(roles.size());
      roles.stream().forEach( (role) ->authorities.add(new SimpleGrantedAuthority(role.getName())));
        return authorities;
    }
 
    public List<Role> getRoles() {
        return roles;
    }
 
    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }
}

_______________________________________________________________________________________________________

嗯.以上就是對SpringSecurity配置的理解,不足之處請小伙伴留言/生活加油!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM