本文應用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(); } }