@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService detailsService;
@Autowired
private SignedUsernamepasswordAuthenticationProvider provider;
@Autowired
private RememberMeAuthenticationProvider RememberMeprovider;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Resource
private SessionRegistry sessionRegistry;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//添加用户验证
auth.authenticationProvider(provider);
auth.authenticationProvider(RememberMeprovider);
// 不删除凭据,以便记住用户
auth.eraseCredentials(false);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 解决不允许显示在iframe的问题
http.headers().frameOptions().disable();
// 自定义过滤器
MyFilterSecurityInterceptor filterSecurityInterceptor = new MyFilterSecurityInterceptor(securityMetadataSource,
accessDecisionManager(), authenticationManager);
// 在适当的地方加入
// 此处可以加入logoutFilter
http.addFilterAt(cuzLogoutFilter(), LogoutFilter.class);
// 此处可以添加remmebermeAuth..FILTER
http.addFilterAfter(rememberMeAuthenticationFilter(),
RememberMeAuthenticationFilter.class);
// 添加UsernamePasswordAuthenticationFilter
http.addFilterAt(myUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
//session并发控制过滤器
http.addFilterAt(new ConcurrentSessionFilter(sessionRegistry,sessionInformationExpiredStrategy()),ConcurrentSessionFilter.class);
// 此处可以添加filterSecurityInterceptor..FILTER
// http.addFilterBefore(filterSecurityInterceptor,FilterSecurityInterceptor.class);
AccessDeniedHandler accessDeniedHandler = new CustomAccessDeniedHandler("/error/illegalAccess");
http // .csrf().disable()//取消CSRF
.authorizeRequests().antMatchers("/css/**").permitAll().antMatchers("/login").permitAll().anyRequest()// all
.authenticated().and().formLogin().loginPage("/login").permitAll().defaultSuccessUrl("/home", true)
.failureUrl("/login?error").and().logout().clearAuthentication(true).logoutUrl("/logout")
.logoutSuccessUrl("/login").and().sessionManagement().invalidSessionUrl("/login").maximumSessions(1)
.expiredUrl("/login").and().and().exceptionHandling().accessDeniedPage("/accessDenied")// .accessDeniedHandler(accessDeniedHandler)//拒绝访问时跳转
.and()
// .rememberMe()//启用记住我功能
// .tokenValiditySeconds(2419200)//记住我四周
// .key("manageKey")
;
// http.exceptionHandling().authenticationEntryPoint(new
// LoginUrlAuthenticationEntryPoint("/login")).and().logout().logoutUrl("/logout").logoutSuccessUrl("/login").and().exceptionHandling().accessDeniedPage("/accessDenied");
super.configure(http);
}
// logoutFilter
@Bean
public LogoutFilter cuzLogoutFilter() {
CuzLogoutFilter filter = new CuzLogoutFilter("/login", customLogoutHandler());
return filter;
}
public LogoutHandler[] customLogoutHandler() {
return new LogoutHandler[] { new SecurityContextLogoutHandler(), new CustomLogoutHandler(),
tokenBasedRememberMeServices() };
}
@Override
public void configure(WebSecurity web) throws Exception {
// 图片资源不拦截
web.ignoring().antMatchers("/app/**");
web.ignoring().antMatchers("/common/**");
web.ignoring().antMatchers("/app/css/**");
web.ignoring().antMatchers("*/images/**");
web.ignoring().antMatchers("/app/js/**");
web.ignoring().antMatchers("/plugins/**");
web.ignoring().antMatchers("/favicon.ico");
web.ignoring().antMatchers("/login/captcha");
web.ignoring().antMatchers("/error/illegalAccess");
super.configure(web);
}
// session失效跳转
private SessionInformationExpiredStrategy sessionInformationExpiredStrategy() {
return new SimpleRedirectSessionInformationExpiredStrategy("/login");
}
@Bean
public SessionRegistry sessionRegistry() {
return new CustomSessionRegistryImpl();
}
// SpringSecurity内置的session监听器
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
/**
* 投票器
*/
/**
* AccessdecisionManager在Spring security中是很重要的。
*
* 在验证部分简略提过了,所有的Authentication实现需要保存在一个GrantedAuthority对象数组中。 这就是赋予给主体的权限。
* GrantedAuthority对象通过AuthenticationManager 保存到
* Authentication对象里,然后从AccessDecisionManager读出来,进行授权判断。
*
* Spring Security提供了一些拦截器,来控制对安全对象的访问权限,例如方法调用或web请求。
* 一个是否允许执行调用的预调用决定,是由AccessDecisionManager实现的。 这个 AccessDecisionManager
* 被AbstractSecurityInterceptor调用, 它用来作最终访问控制的决定。
* 这个AccessDecisionManager接口包含三个方法:
*
* void decide(Authentication authentication, Object secureObject,
* List config) throws AccessDeniedException; boolean
* supports(ConfigAttribute attribute); boolean supports(Class clazz);
*
* 从第一个方法可以看出来,AccessDecisionManager使用方法参数传递所有信息,这好像在认证评估时进行决定。
* 特别是,在真实的安全方法期望调用的时候,传递安全Object启用那些参数。 比如,让我们假设安全对象是一个MethodInvocation。
* 很容易为任何Customer参数查询MethodInvocation,
* 然后在AccessDecisionManager里实现一些有序的安全逻辑,来确认主体是否允许在那个客户上操作。
* 如果访问被拒绝,实现将抛出一个AccessDeniedException异常。
*
* 这个 supports(ConfigAttribute) 方法在启动的时候被
* AbstractSecurityInterceptor调用,来决定AccessDecisionManager
* 是否可以执行传递ConfigAttribute。 supports(Class)方法被安全拦截器实现调用,
* 包含安全拦截器将显示的AccessDecisionManager支持安全对象的类型。
*/
private AbstractAccessDecisionManager accessDecisionManager() {
List> decisionVoters = new ArrayList();
decisionVoters.add(new AuthenticatedVoter());
decisionVoters.add(new RoleVoter());// 角色投票器,默认前缀为ROLE_
RoleVoter AuthVoter = new RoleVoter();
AuthVoter.setRolePrefix("AUTH_");// 特殊权限投票器,修改前缀为AUTH_,用于自定义角色
decisionVoters.add(AuthVoter);
AbstractAccessDecisionManager accessDecisionManager = new AffirmativeBased(decisionVoters);//一票通过投票策略,还可以选用一票否决策略,多票通过策略,也可以自定义需求
//CustomAccessDecisionManager accessDecisionManager1 = new CustomAccessDecisionManager();// 用自定义的一票通过投票策略//此处采用用户自定义的投票器
//改成系统定义的策略器,因为自定义的虽然至此任意前缀的权限,但也意味着管理的复制性
return accessDecisionManager;
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() {
AuthenticationManager authenticationManager = null;
try {
authenticationManager = super.authenticationManagerBean();
} catch (Exception e) {
e.printStackTrace();
}
return authenticationManager;
}
/**
* 验证异常处理器,登录失败后调用
*其配置进CuzUsernamePasswordAuthenticationFilter中
* @return
*/
private SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler() {
return new SimpleUrlAuthenticationFailureHandler("/login");
}
/**
* 登录成功后跳转 如果需要根据不同的角色做不同的跳转处理,那么继承AuthenticationSuccessHandler重写方法
*其配置进CuzUsernamePasswordAuthenticationFilter中
* @return
*/
private SimpleUrlAuthenticationSuccessHandler authenticationSuccessHandler() {
return new SimpleUrlAuthenticationSuccessHandler("/home");
}
@Bean
public CuzUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter() throws Exception {
CuzUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter = new CuzUsernamePasswordAuthenticationFilter();
myUsernamePasswordAuthenticationFilter.setPostOnly(true);
myUsernamePasswordAuthenticationFilter.setAuthenticationManager(this.authenticationManager());
myUsernamePasswordAuthenticationFilter.setUsernameParameter("username");
myUsernamePasswordAuthenticationFilter.setPasswordParameter("password");
// myUsernamePasswordAuthenticationFilter.set
// myUsernamePasswordAuthenticationFilter.setVerificationCodeParameter("verification_code");
myUsernamePasswordAuthenticationFilter
.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login", "POST"));
myUsernamePasswordAuthenticationFilter.setAuthenticationFailureHandler(simpleUrlAuthenticationFailureHandler());
myUsernamePasswordAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
myUsernamePasswordAuthenticationFilter.setSessionAuthenticationStrategy(
new CustomConcurrentSessionControlAuthenticationStrategy(sessionRegistry));
myUsernamePasswordAuthenticationFilter.setRememberMeServices(tokenBasedRememberMeServices());
// myUsernamePasswordAuthenticationFilter
return myUsernamePasswordAuthenticationFilter;
}
public static class MyFilterSecurityInterceptor extends FilterSecurityInterceptor {
//其作用在与在request中加入"__spring_security_filterSecurityInterceptor_filterApplied属性
//同时管理调用资源文件对应的权限
public MyFilterSecurityInterceptor(FilterInvocationSecurityMetadataSource securityMetadataSource,
AccessDecisionManager accessDecisionManager, AuthenticationManager authenticationManager) {
this.setSecurityMetadataSource(securityMetadataSource);// 加入资源管理器
this.setAccessDecisionManager(accessDecisionManager);// 加入决策管理器
this.setAuthenticationManager(authenticationManager);// 加入验证管理器
}
}
@Bean
public FilterInvocationSecurityMetadataSource MyFilterInvocationSecurityMetadataSource() {
return new CustomInvocationSecurityMetadataSource();
}
// 配置remmeber me
@Bean
public TokenBasedRememberMeServices tokenBasedRememberMeServices() {
// 自定义RememberMeService,可以加入数据库操作,比如当设置用户使用退出功能退出应用后,下次remmeberme功能不可用,当前系统未添加此功能
TokenBasedRememberMeServices tbrms = new CustomTokenBasedRememberMeServices("_spring_security_Key",
detailsService);
// 设置cookie过期时间为2天
tbrms.setTokenValiditySeconds(60 * 60 * 24 * 2);
// 设置checkbox的参数名为rememberMe(默认为remember-me),注意如果是ajax请求,参数名不是checkbox的name而是在ajax的data里
tbrms.setParameter("_spring_security_remember_me");
tbrms.setCookieName("_spring_security_Key");
return tbrms;
}
@Bean
public RememberMeAuthenticationProvider rememberMeAuthenticationProvider() {
RememberMeAuthenticationProvider rmap = new CustomRememberMeAuthenticationProvider("_spring_security_Key");
return rmap;
}
@Bean
public RememberMeAuthenticationFilter rememberMeAuthenticationFilter() throws Exception {
// 自定义RememberMeAuthenticationFilter,可添加额外操作
RememberMeAuthenticationFilter myFilter = new CuzRememberMeAuthenticationFilter(authenticationManager,
tokenBasedRememberMeServices());
return myFilter;
}
}
安全配置文件,首先继承WebSecurityConfigurerAdapter类,相关的pom配置请查看spring网站的依赖。
@EnableWebSecurity和@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)加载class上,以便通知spring boot初始化时初始化相关的WebSecurity的bean.@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)是用来使能方法安全的,按自己需求看看是否需要。
然后就可以去具体配置了哈。
为了实现登录效验,我们实现相关filter,然后重载configure(HttpSecurity http),然后按照官方的fiter次序依次添加我们需要的fiter.在上面的代码片段中,fiter都是自定义的,也较容易,依次继承相关的fiter,写自己的实现就ok了。
我们通过spring官方网站了解知道,验证是通过auth..manager调用provider来实现的。因此,需要配置provider.
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//添加用户验证
auth.authenticationProvider(provider);
auth.authenticationProvider(RememberMeprovider);
// 不删除凭据,以便记住用户
auth.eraseCredentials(false);
}
我定义了两个provider.分别实现AuthenticationProvider接口,继承RememberMeAuthenticationProvider(spring自带的RememberMeAuthenticationProvider好像是空的哈)。
不多说,贴代码:
public class CustomRememberMeAuthenticationProvider extends RememberMeAuthenticationProvider{
public CustomRememberMeAuthenticationProvider(String key) {
super(key);
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!supports(authentication.getClass())) {
return null;
}
if (super.getKey().hashCode() != ((RememberMeAuthenticationToken) authentication)
.getKeyHash()) {
throw new BadCredentialsException(
messages.getMessage("RememberMeAuthenticationProvider.incorrectKey",
"The presented RememberMeAuthenticationToken does not contain the expected key"));
}
return authentication;
}
public boolean supports(Class> authentication) {
return (RememberMeAuthenticationToken.class.isAssignableFrom(authentication));
}
}
public class SignedUsernamepasswordAuthenticationProvider implements AuthenticationProvider {
@Autowired
private CustomUserDetailsService userService;
/**
* 自定义验证方式
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!supports(authentication.getClass())) {
return null;
}
String username = authentication.getName();
String password = (String) authentication.getCredentials();
SigedUserDetails user = (SigedUserDetails) userService.loadUserByUsername(username);
if(user == null){
throw new BadCredentialsException("Username not found.");
}
//加密过程在这里体现
;//admin:ab4ad1624d29173c70d739c389e1daa3,Q123456W:2db436d701334d81c11ad8eb781e92c2
if (!MD5Util.encrypt(password).equals(user.getPassword())) {
throw new BadCredentialsException("Wrong password.");
}
Collection extends GrantedAuthority> authorities = user.getAuthorities();
return new UsernamePasswordAuthenticationToken(user, password, authorities);
}
@Override
public boolean supports(Class> authentication) {
// TODO Auto-generated method stub
//return true;
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
@Slf4j
public class CustomSessionRegistryImpl implements SessionRegistry, ApplicationListener
private static final String SESSIONIDS = "sessionIds";
private static final String PRINCIPALS = "principals";
@Autowired
private RedisTemplate redisTemplate;
// private final ConcurrentMap
贴代码真麻烦!这两个代码是实现分布式session的,如果没有这个需求可以去掉的。
注意,本人在用了这个session策略后,rememberme老重复登录,检查发现,需要在public class CuzRememberMeAuthenticationFilter extends RememberMeAuthenticationFilter实现时重载dofiter方法,然后在。。贴代码吧
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (SecurityContextHolder.getContext().getAuthentication() == null) {
Authentication rememberMeAuth = getRememberMeServices().autoLogin(request,
response);
if (rememberMeAuth != null) {
// Attempt authenticaton via AuthenticationManager
try {
rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);
// Store to SecurityContextHolder
SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
SigedUserDetails userdelail = (SigedUserDetails) rememberMeAuth.getPrincipal();
Collection extends GrantedAuthority> authorities = userdelail.getAuthorities();
Authentication auth = new UsernamePasswordAuthenticationToken(
userdelail.getUserName(), userdelail.getPassword(), authorities);
SecurityContextHolder.getContext().setAuthentication(auth);
SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
CustomConcurrentSessionControlAuthenticationStrategy sessionStrategy = new CustomConcurrentSessionControlAuthenticationStrategy(sessionRegistry);
sessionStrategy.onAuthentication(auth, request, response);
sessionStrategy.onAuthentication(rememberMeAuth, request, response);
onSuccessfulAuthentication(request, response, rememberMeAuth);
if (logger.isDebugEnabled()) {
logger.debug("SecurityContextHolder populated with remember-me token: '"
+ SecurityContextHolder.getContext().getAuthentication()
+ "'");
}
// Fire event
if (this.eventPublisher != null) {
eventPublisher
.publishEvent(new InteractiveAuthenticationSuccessEvent(
SecurityContextHolder.getContext()
.getAuthentication(), this.getClass()));
}
if (successHandler != null) {
successHandler.onAuthenticationSuccess(request, response,
rememberMeAuth);
return;
}
}
catch (AuthenticationException authenticationException) {
if (logger.isDebugEnabled()) {
logger.debug(
"SecurityContextHolder not populated with remember-me token, as "
+ "AuthenticationManager rejected Authentication returned by RememberMeServices: '"
+ rememberMeAuth
+ "'; invalidating remember-me token",
authenticationException);
}
rememberMeServices.loginFail(request, response);
onUnsuccessfulAuthentication(request, response,
authenticationException);
}
}
chain.doFilter(request, response);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
chain.doFilter(request, response);
}
}
把自己的user..token保存进去。就ok了。写这篇博客主要也是为了这个事情,当时弄了几天额。。。。