本文应用Spring Security 4.2.3 ,并采用Java config搭建实例,如果有不对之处望读者指出。
1、pom.xml中引入相关依赖。
<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>
2、配置DelegatingFilterProxy ,在存放项目配置的包目录下创建一个扩展AbstractSecurityWebApplicationInitializer 的新类即可。
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 {
}
3、配置 SecurityConfig类。SecurityConfig类需配置在WebApplicationContext(类似web.xml)中。
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());
}
}
4、参考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;
}
}
5、自定义UserDetailsService,用于从数据库、内存或其他途径获取用户的用户名、密码、身份信息。本例从数据库中获取到用户的用户名和密码后直接添加用户身份。
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;
}
}
6、自定义AuthenticationProvider,通过对比UsernamePasswordAuthenticationFilter中获取到的用户登录信息与UserDetailsService中查询到的用户信息,确认用户的身份。
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);
}
}
7、自定义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();
}
}
8、自定义AuthenticationFailureHandler,使登录失败时返回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.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();
}
}
9、自定义的AccessDeniedHandler Http403ForbiddenEntryPointCustom 方法同上。
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();
}
}
引用来自“爪哇小贩”的评论
自定义认证做的很全,遗憾的是缺少自定义授权部分啊引用来自“爪哇小贩”的评论
自定义认证做的很全,遗憾的是缺少自定义授权部分啊引用来自“源子”的评论
您所指的缺少的自定义授权部分是?:smiley:.antMatchers( "/index.html")
.permitAll()
.antMatchers("/userOperation1","/userOperation2")
.hasRole("USER").anyRequest().authenticated().and();
现在是硬编码,应该到数据库去获得url和角色
引用来自“爪哇小贩”的评论
自定义认证做的很全,遗憾的是缺少自定义授权部分啊引用来自“源子”的评论
您所指的缺少的自定义授权部分是?:smiley:引用来自“爪哇小贩”的评论
就是这部分http.authorizeRequests().antMatchers( "/index.html")
.permitAll()
.antMatchers("/userOperation1","/userOperation2")
.hasRole("USER").anyRequest().authenticated().and();
现在是硬编码,应该到数据库去获得url和角色