Spring Security 4.2.3 採用 Java Config 實現安全訪問控制實例

本文應用Spring Security 4.2.3 ,並採用Java config搭建實例,若是有不對之處望讀者指出。html

一、pom.xml中引入相關依賴。java

<dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-web</artifactId>
      <version>4.2.3.RELEASE</version>
      <scope>compile</scope>
</dependency>
<dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-config</artifactId>
      <version>4.2.3.RELEASE</version>
      <scope>compile</scope>
</dependency>

二、配置DelegatingFilterProxy ,在存放項目配置的包目錄下建立一個擴展AbstractSecurityWebApplicationInitializer 的新類便可。web

package com.my.security.appconfig;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

@Configuration
public class SpringSecutityInitializer extends AbstractSecurityWebApplicationInitializer {
	
}

三、配置 SecurityConfig類。SecurityConfig類需配置在WebApplicationContext(相似web.xml)中。spring

package com.my.security.appconfig;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import com.my.security.access.config.AccessDeniedHandlerCustom;
import com.my.security.access.config.AuthenticationFailureHandlerCustom;
import com.my.security.access.config.AuthenticationProviderCustom;
import com.my.security.access.config.AuthenticationSuccessHandlerCustom;
import com.my.security.access.config.Http403ForbiddenEntryPointCustom;
import com.my.security.access.config.UserDetailsServiceCustom;
import com.my.security.access.config.UsernamePasswordAuthenticationFilterCustom;

/**
 * 
 * @description Spring security java config
 *
 * @author yuanzi
 * @time 2017年6月13日 下午4:03:37
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //配置自定義的AuthenticationProvider  authenticationProvider經過對比用戶輸入的登陸信息與userDetailsService中獲取到的數據庫中用戶信息確認用戶賦權
	@Bean
	public AuthenticationProvider authenticationProvider() {
		AuthenticationProvider authenticationProvider = new AuthenticationProviderCustom(userDetailsService());
		return authenticationProvider;
	}

	//配置自定義的UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter用於過濾用戶的登陸請求
	@Bean
	public UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter() throws Exception {
		UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter = new UsernamePasswordAuthenticationFilterCustom();
		//配置自定義的authenticationSuccessHandler();authenticationFailureHandler();authenticationManager();
		usernamePasswordAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
		usernamePasswordAuthenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
		usernamePasswordAuthenticationFilter.setAuthenticationManager(authenticationManager());
		return usernamePasswordAuthenticationFilter;
	}

	//配置自定義的UserDetailsService userDetailsService用於從數據庫中或其餘途徑獲取到用戶的用戶名、密碼、權限等信息。
	@Bean
	public UserDetailsService userDetailsService() {
		UserDetailsService userDetailsService = new UserDetailsServiceCustom();
		return userDetailsService;
	}

	//配置自定義的AuthenticationSuccessHandler authenticationSuccessHandler用於返回用戶登陸成功的結果
	@Bean
	public AuthenticationSuccessHandler authenticationSuccessHandler() {
		AuthenticationSuccessHandler authenticationSuccessHandler = new AuthenticationSuccessHandlerCustom();
		return authenticationSuccessHandler;
	}

	//配置自定義的AuthenticationFailureHandler authenticationFailureHandler用於返回用戶登陸失敗的結果
	@Bean
	public AuthenticationFailureHandler authenticationFailureHandler() {
		AuthenticationFailureHandler authenticationFailureHandler = new AuthenticationFailureHandlerCustom();
		return authenticationFailureHandler;
	}

	//配置自定義的AccessDeniedHandler accessDeniedHandler用於返回用戶已經過身份驗證,可是訪問無權限訪問的資源時的結果
	@Bean
	public AccessDeniedHandler accessDeniedHandler() {
		AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerCustom();
		return accessDeniedHandler;
	}

	//配置自定義的Http403ForbiddenEntryPoint http403ForbiddenEntryPoint用於返回用戶未經過權限驗證的結果
	@Bean
	public Http403ForbiddenEntryPoint http403ForbiddenEntryPoint() {
		Http403ForbiddenEntryPoint http403ForbiddenEntryPoint = new Http403ForbiddenEntryPointCustom();
		return http403ForbiddenEntryPoint;
	}

    //配置AuthenticationManager
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.authenticationProvider(authenticationProvider());
	}

	//配置須要忽略的一些靜態資源等
	@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring().antMatchers("/resources/**");
	};

	//配置url權限
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
				.antMatchers( "/index.html")
				.permitAll()
				.antMatchers("/userOperation1","/userOperation2")
				.hasRole("USER").anyRequest().authenticated().and();
	    //配置自定義的usernamePasswordAuthenticationFilter();
		http.addFilterAt(usernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
		//禁用了csrf,默認是不由用。實際不該禁用。
		http.csrf().disable();
		//配置自定義的accessDeniedHandler();http403ForbiddenEntryPoint();
		http.exceptionHandling().accessDeniedHandler(accessDeniedHandler());
		http.exceptionHandling().authenticationEntryPoint(http403ForbiddenEntryPoint());
	}

}

四、參考UsernamePasswordAuthenticationFilter的源碼自定義UsernamePasswordAuthenticationFilterCustom類,本例針對多登錄形式,用戶名密碼登陸和用戶名驗證碼登陸作了相應代碼編寫。數據庫

package com.my.security.access.config;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.Assert;

import com.my.security.common.utils.EncodeToUserPassWordBySHA;
import com.my.security.database.beans.UserBean;
import com.my.security.database.dao.UserDao;

/**
 * @description 自定義 UsernamePasswordAuthenticationFilter 方法,增長驗證碼登陸的相關內容,編寫參考UsernamePasswordAuthenticationFilter源碼
 *
 * @author yuanzi
 * @time 2017年6月15日 下午4:39:45
 */
public class UsernamePasswordAuthenticationFilterCustom extends UsernamePasswordAuthenticationFilter {

	private static final Logger log = LogManager.getLogger(UsernamePasswordAuthenticationFilterCustom.class);

	@Autowired
	private UserDao userDao;

	private static final String SPRING_SECURITY_VALIDATE_CODE_KEY = "validateCode";

	private String validateCodeParameter = SPRING_SECURITY_VALIDATE_CODE_KEY;

	public UsernamePasswordAuthenticationFilterCustom() {
		//自定義用戶名密碼的參數名
		this.setUsernameParameter("phone");
		this.setPasswordParameter("userPass");
		//配置登陸過濾路徑
		super.setFilterProcessesUrl("/services/login/**");
	}

	//重寫attemptAuthentication方法,
	@Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException {
		if (!request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
		}
        
		String username = obtainUsername(request) == null ? "" : obtainUsername(request).trim();
		String password = obtainPassword(request) == null ? "" : obtainPassword(request).trim();
		String validateCode = obtainValidateCode(request) == null ? "" : obtainValidateCode(request).trim();

		log.info("username:" + username + "password:" + password + "validateCode:" + validateCode);
		UsernamePasswordAuthenticationToken authRequest = null;
		if (request.getRequestURL().toString().contains("byPassword")) {
			if (username.equals("")) {
			    throw new AuthenticationServiceException("username parameter must not be empty or null!");
			}else if(password.equals("")&&validateCode.equals("")){
			    throw new AuthenticationServiceException("password or validateCode parameter must not be empty or null!");
			}  else {
				UserBean userBean = userDao.findUserInfoByPhone(username);
				if(null==userBean){
					throw new AuthenticationServiceException("user does not exist;");
				}
				String SHAPassword = EncodeToUserPassWordBySHA.encodeToUserPassWord(password, userBean.getSalt());
				authRequest = new UsernamePasswordAuthenticationToken(username + "_PW", SHAPassword);
			}
		} else if (request.getRequestURL().toString().contains("byValidateCode")) {
			authRequest = new UsernamePasswordAuthenticationToken(username + "_VC", validateCode);
		}else{
			throw new AuthenticationServiceException("login method not supported: " + request.getRequestURL().toString());
		}
		
		setDetails(request, authRequest);

		return this.getAuthenticationManager().authenticate(authRequest);

	}

	protected String obtainValidateCode(HttpServletRequest request) {
		return request.getParameter(validateCodeParameter);
	}

	public String getValidateCodeParameter() {
		return validateCodeParameter;
	}

	public void setValidateCodeParameter(String validateCodeParameter) {
		Assert.hasText(validateCodeParameter, "validateCode parameter must not be empty or null");
		this.validateCodeParameter = validateCodeParameter;
	}
}

五、自定義UserDetailsService,用於從數據庫、內存或其餘途徑獲取用戶的用戶名、密碼、身份信息。本例從數據庫中獲取到用戶的用戶名和密碼後直接添加用戶身份。apache

package com.my.security.access.config;

import java.util.ArrayList;
import java.util.Collection;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.my.security.database.beans.UserBean;
import com.my.security.database.dao.UserDao;

/**
 * @description userDetailService 獲取用戶名對應權限
 *
 * @author yuanzi
 * @time 2017年6月16日 下午2:17:27
 */
public class UserDetailsServiceCustom implements UserDetailsService {

    @Autowired
    private UserDao userDao;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Collection<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
        String userName = username.replace("_PW", "").replace("_VC", "");
        //不一樣filter的執行順序不一樣,authentication中的username在以前UsernamePasswordAuthenticationFilterCustom的代碼中加入了後綴,在userDetails的方法被調用的時候,傳入的username多是已經被修改過的。
        UserBean userBean = userDao.findUserInfoByPhone(userName);
        if (null != userBean) {
            SimpleGrantedAuthority auth_app_user = new SimpleGrantedAuthority("ROLE_USER");
            auths.add(auth_app_user);
        }else{
            return null;
        }

        User user = new User(userName, userBean.getUserPass(), true, true, true, true, auths);

        return user;
    }

}

六、自定義AuthenticationProvider,經過對比UsernamePasswordAuthenticationFilter中獲取到的用戶登陸信息與UserDetailsService中查詢到的用戶信息,確認用戶的身份。json

package com.my.security.access.config;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.my.common.cache.CacheClient;

/**
 * @description 經過比對登陸信息與數據庫中信息確認用戶身份是否正確
 *
 * @author yuanzi
 * @time 2017年6月19日 下午3:59:37
 */
public class AuthenticationProviderCustom implements AuthenticationProvider {

	private static final Logger log = LogManager.getLogger(AuthenticationProviderCustom.class);

	@Autowired
	private CacheClient<String> client;

	private final UserDetailsService userDetailsService;

	public AuthenticationProviderCustom(UserDetailsService userDetailsService) {
		this.userDetailsService = userDetailsService;
	}

	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {

		log.info("username:" + authentication.getName() + "password:" + String.valueOf(authentication.getCredentials()));
		String[] username_flag = authentication.getName().split("_");
		String username = username_flag[0];
		String flag = username_flag[1];
		String password = String.valueOf(authentication.getCredentials());

		UserDetails userDetails = null;

		if (null != username || "" == username) {
			userDetails = userDetailsService.loadUserByUsername(username);
		}

		if (userDetails == null) {
			throw new UsernameNotFoundException("Invalid username/password");
		} else if (!userDetails.isEnabled()) {
			throw new DisabledException("User is disenabled");
		} else if (!userDetails.isAccountNonExpired()) {
			throw new AccountExpiredException("Account has expired");
		} else if (!userDetails.isAccountNonLocked()) {
			throw new LockedException("Account is locked");
		} else if (!userDetails.isCredentialsNonExpired()) {
			throw new LockedException("Credential is expired");
		}

		String SHAPassword = userDetails.getPassword();

		if (flag.equals("PW")) {
			if (!password.equals(SHAPassword)) {
				throw new BadCredentialsException("password error!");
			}
		} else if (flag.equals("VC")) {
			//從緩存中獲取驗證碼
			String validateCodeInCache = client.get("validateCode" + username);
			if (!validateCodeInCache.equals(password)) {
				throw new BadCredentialsException("validateCode error!");
			}
		}
		return new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());

	}

	@Override
	public boolean supports(Class<?> authentication) {
		return UsernamePasswordAuthenticationToken.class.equals(authentication);
	}

}

七、自定義AuthenticationSuccessHandler,使登陸成功時返回JSON結果緩存

package com.my.security.access.config;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;

import com.my.security.bussiness.service.UserInformationService;
import com.my.security.database.beans.UserBean;

import net.sf.json.JSONObject;

/**
 * @description 自定義AuthenticationSuccessHandler
 *
 * @author yuanzi
 * @time 2017年07月03日 上午09:39:45
 */
public class AuthenticationSuccessHandlerCustom extends SimpleUrlAuthenticationSuccessHandler {
	@Autowired
	private UserInformationService userInformationService;

	private static final Logger log = LogManager.getLogger(AuthenticationSuccessHandlerCustom.class);
	private JSONObject result = new JSONObject();
	private JSONObject object = new JSONObject();
	private JSONObject data = new JSONObject();

	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth)
			throws IOException, ServletException {
		result.put("code", 0);
		result.put("info", "login success!");
		UserBean userBean = userInformationService.findUserInfoByPhone(request.getParameter("phone"));
		if (userBean != null) {
			object = JSONObject.fromObject(userBean);
		}
		data.put("result", result);
		data.put("object", object);
		
                response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
		response.getWriter().print(data.toString());
		response.getWriter().flush();

	}

}

八、自定義AuthenticationFailureHandler,使登陸失敗時返回JSON結果,經過拋出的異常判斷是哪一種狀況形成的失敗。app

package com.my.security.access.config;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;

import net.sf.json.JSONObject;

/**
 * @description 自定義AuthenticationFailureHandler
 *
 * @author yuanzi
 * @time 2017年07月03日 上午09:40:45
 */
public class AuthenticationFailureHandlerCustom extends SimpleUrlAuthenticationFailureHandler {

	private static final Logger log = LogManager.getLogger(AuthenticationFailureHandlerCustom.class);
	private JSONObject result = new JSONObject();
	private JSONObject object = new JSONObject();
	private JSONObject data = new JSONObject();

	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException exception) throws IOException, ServletException {
		result.put("code", 1);
		result.put("info", exception.getMessage());

		data.put("result", result);
		data.put("object", object);

                response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
		response.getWriter().print(data.toString());
		response.getWriter().flush();
	}

}

九、自定義的AccessDeniedHandler Http403ForbiddenEntryPointCustom 方法同上。ide

package com.my.security.access.config;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import net.sf.json.JSONObject;

/**
 * @description 自定義AccessDeniedHandler 返回用戶訪問無權限訪問的資源的結果
 *
 * @author yuanzi
 * @time 2017年07月03日 上午09:40:45
 */
public class AccessDeniedHandlerCustom implements AccessDeniedHandler {
	private static final Logger log = LogManager.getLogger(AccessDeniedHandlerCustom.class);

	private JSONObject result = new JSONObject();
	private JSONObject object = new JSONObject();
	private JSONObject data = new JSONObject();

	@Override
	public void handle(HttpServletRequest request, HttpServletResponse response,
			AccessDeniedException accessDeniedException) throws IOException, ServletException {
		result.put("code", 1);
		result.put("info", "Access Denied");

		data.put("result", result);
		data.put("object", object);

                response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
		response.getWriter().print(data.toString());
		response.getWriter().flush();
	}

}
package com.my.security.access.config;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;

import net.sf.json.JSONObject;

/**
 * @description 自定義Http403ForbiddenEntryPointCustom 返回用戶未經過權限驗證的結果(例如未登陸直接調用接口)
 *
 * @author yuanzi
 * @time 2017年07月03日 上午09:40:45 
 */
public class Http403ForbiddenEntryPointCustom extends Http403ForbiddenEntryPoint {

	private static final Log log = LogFactory.getLog(Http403ForbiddenEntryPointCustom.class);

	private JSONObject result = new JSONObject();
	private JSONObject object = new JSONObject();
	private JSONObject data = new JSONObject();

	@Override
	public void commence(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException authenticationException) throws IOException, ServletException {
		result.put("code", 1);
		result.put("info", "Access Denied");

		data.put("result", result);
		data.put("object", object);

                response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
		response.getWriter().print(data.toString());
		response.getWriter().flush();
	}
}
相關文章
相關標籤/搜索