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