SpringSecurity 基礎應用

工作流程: 

1.過濾器filter 檢查 (filter要設置manager)   

2.provider 來 校驗 授權  (manage加載provider)   

3.EntryPoint來處理 授權可能會出現的異常







1.pom.xml中導入依賴

<denpendency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-security<artifactId>

</denpendency>


2.配置類 SecurityConfig

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private Parameters parameters;

    @Autowired
    private CommonCacheUtils cacheUtils;

    //設置 filter的get方法, 爲其配置 AuthenticationManager  private RestPreAuthenticatedProccessingFilter getRestPreAuuthenticatedProcessingFilter() throws Exception {
        RestPreAuthenticatedProccessingFilter filter=new RestPreAuthenticatedProccessingFilter(parameters.getSpringSecurityPermitUrl(),cacheUtils);
        filter.setAuthenticationManager(this.authenticationManagerBean());
        return filter;
    }


    @Override    //AuthenticationManager 構造器  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(new RestAuthenticationProvider()); //爲manager添加一個Provider  }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers(parameters.getSpringSecurityPermitUrl().toArray(new String[parameters.getSpringSecurityPermitUrl().size()])).permitAll()
                .anyRequest().authenticated()
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)  //不需要session,無狀態的請求  .and().httpBasic().authenticationEntryPoint(new RestAuthenticationEntryPoint())
                .and().addFilter(getRestPreAuuthenticatedProcessingFilter())
        ;
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(HttpMethod.OPTIONS,"/**"); //忽略 攔截option方法的請求  }
}


3.自定義filter 檢查 請求的信息

package com.sean.mamabike.security;

import com.sean.mamabike.cache.CommonCacheUtils;
import com.sean.mamabike.common.constants.Constants;
import com.sean.mamabike.user.entity.UserElement;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.util.AntPathMatcher;

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

@Slf4j
//spring security的 預 過濾器 public class RestPreAuthenticatedProccessingFilter extends AbstractPreAuthenticatedProcessingFilter {

    private AntPathMatcher matcher=new AntPathMatcher(); //spring 提供的 路徑 匹配器   private List<String> springSecurityPermitUrl;  //需要放過的請求列表 構造器用   private CommonCacheUtils cacheUtils;  //redis 工具   public RestPreAuthenticatedProccessingFilter(List<String> springSecurityPermitUrl, CommonCacheUtils cacheUtils) {
        this.springSecurityPermitUrl = springSecurityPermitUrl;
        this.cacheUtils = cacheUtils;
    }

    @Override    // 獲取 用戶信息  protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
        List<GrantedAuthority> authorityList=new ArrayList<>();  //授權列表   //1.不需要 權限 檢查 的請求(login,register.....  if (isPermitUrl(request.getRequestURI().toString())||"OPTIONS".equals(request.getMethod())){
            GrantedAuthority grantedAuthority=new SimpleGrantedAuthority("USER_OTHERS");  //授權了一個 其他 權限  authorityList.add(grantedAuthority);  //授權列表 加載 角色   return new RestAuthenticationToken(authorityList);  //用戶信息  }


        // 2. 其他 正常的 請求  String version=request.getHeader(Constants.REQUEST_VERSION); //檢查app版本  String token=request.getHeader(Constants.REQUEST_TOKEN);

        if (version == null){   //若版本信息爲空  request.setAttribute("header_error_code",400);  //設置錯誤信息  }

        if (request.getHeader("header_error_code") == null){  //如果沒有錯誤信息,就檢查token  try{
                if (!StringUtils.isBlank(token)) { //token不爲空  UserElement ue = cacheUtils.getUserElementByToken(token);   //獲取redis裏的ue  if (ue instanceof UserElement) {
                        GrantedAuthority authority = new SimpleGrantedAuthority("USER_BIKE"); //授權一個 單車 權限  authorityList.add(authority);

                        RestAuthenticationToken authToken = new RestAuthenticationToken(authorityList);
                        authToken.setUserElement(ue);

                        return authToken;

                    } else {
                        log.warn("token is empty");
                        request.setAttribute("header_error_code", 401);
                    }
                }
            }catch (Exception e){
                log.error("fail to authenticate user ",e);
            }
        }


        //3. 請求頭有錯誤信息  if (request.getHeader("header_error_code") != null){
            GrantedAuthority authority=new SimpleGrantedAuthority("USER_Error");  //設置一個 錯誤 權限  authorityList.add(authority);
        }
        return new RestAuthenticationToken(authorityList);

    }

    //匹配請求 路徑,判斷是否放過  private boolean isPermitUrl(String uri) {
        boolean result=false;
        if (springSecurityPermitUrl != null){
            for (String s:springSecurityPermitUrl){
                if(matcher.match(s,uri)){
                    result=true;
                    break;
                }
            }
        }
        return result;
    }

    @Override    //獲取 密碼  protected Object getPreAuthenticatedCredentials(HttpServletRequest httpServletRequest) {
        return null;
    }
}


4.自定義provider 判斷是否通過security驗證

package com.sean.mamabike.security;

import com.sean.mamabike.common.exception.BadCredentialException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;

//spring security 的provider 來 校驗 身份信息 public class RestAuthenticationProvider implements AuthenticationProvider {

    /**
      *@Author sean
      *@Date 2017/10/14 16:17
      *@Description 第二步:校驗信息,授權
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        if (authentication instanceof PreAuthenticatedAuthenticationToken){
            PreAuthenticatedAuthenticationToken preAuth= (PreAuthenticatedAuthenticationToken) authentication;  //獲取 傳輸的token  RestAuthenticationToken restAuth= (RestAuthenticationToken) preAuth.getPrincipal();  //獲取自定義的token  if (restAuth.getAuthorities() != null&& restAuth.getAuthorities().size()>0) { //獲取權限列表,並且不爲null,大於0  GrantedAuthority authority=restAuth.getAuthorities().iterator().next();
                if ("USER_BIKE".equals(authority.getAuthority())){
                    return restAuth;
                }else if ("USER_OTHERS".equals(authority.getAuthority())){
                    return restAuth;
                }
            }
        }else if (authentication instanceof RestAuthenticationToken){
            RestAuthenticationToken restAuth= (RestAuthenticationToken) authentication;  //獲取自定義的驗證token  if (restAuth.getAuthorities() !=null&&restAuth.getAuthorities().size()>0){
                GrantedAuthority authority=restAuth.getAuthorities().iterator().next();
                if ("USER_BIKE".equals(authority.getAuthority())){
                    return restAuth;
                }else if ("USER_OTHERS".equals(authority.getAuthority())){
                    return restAuth;
                }
            }
        }

        throw new BadCredentialException("unknown");
    }



    /**
      *@Author sean
      *@Date 2017/10/13 16:06
      *@Description 1.判斷傳過來token的類型,進行判斷
     *              如果return true,則執行上面的authenticate  驗證
     *              如果return false, 報異常到entryPoint
     */
    @Override
    public boolean supports(Class<?> authentication) {
        //判斷傳過來的token是否是 定義的token  return PreAuthenticatedAuthenticationToken.class.isAssignableFrom(authentication)||RestAuthenticationToken.class.isAssignableFrom(authentication);
    }
}
 
5.配置 EntryPoint 異常處理
package com.sean.mamabike.security;
import com.alibaba.fastjson.JSON;
import com.sean.mamabike.common.resp.ApiResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.*;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
//統一異常處理 public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        ApiResult resp=new ApiResult();

        //檢查頭部裏面帶的錯誤信息  if ("400".equals(request.getHeader("header_error_code")+"")){
            resp.setCode(400);
            resp.setMessage("請升級至最新客戶端");

        }else if ("401".equals(request.getHeader("header_error_code"))){
            resp.setCode(401);
            resp.setMessage("請登陸後再嘗試");
        }


        // 解決 跨域請求的問題 把結果以json的形式返回給 前端 (前後端分離的時候都要跨域)  try {
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
            response.setHeader("Access-Control-Max-Age", "3600");
            response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            response.getWriter().write(JSON.toJSONString(resp));  //把信息返回給前端  response.flushBuffer();
        } catch (IOException e1) {
            log.error("fail to send error info to user",e.getMessage());
        }


    }
}