您的位置:首页 > 编程语言 > Java开发

Spring Security学习——基于Java注解的Spring Security配置

2018-01-30 14:52 477 查看

              基于Java注解的Spring Security配置

 
        Spring在早期版本的时候,全是基于xml配置文件,导致配置极其繁杂。后期的版本中,Spring提供了对基于Java注解的支持。Java注解的Spring使每个Bean类有了自配置的特性,通过在每个类中添加相应的Java注解,就可以标注这些类在系统中的功能,可以大大减少配置文件的管理。当然,Java注解的Spring也有一些缺点,比如说使用了注解来配置Spring就会让Spring丧失热部署的能力,因为Java注解会编译,编译之后如果还想改的话就必须修改源代码再次编译上线,而如果使用Spring配置文件的话,就可以直接修改配置文件中的内容,Spring框架会自动重新加载配置,实现热部署。

      下面是完全基于注解的Spring MVC+Spring Security项目,在web.xml中没有任何配置。整了一个上午+下午两个小时,终于弄明白是怎么回事了。果然还是要好好了解下Spirng MVC的内部原理和底层实现,Spring MVC启动器可以继承AbstractAnnotationConfigDispatcherServletInitializer类,在getRootConfigClasses()方法中添加继承了WebMvcConfigurerAdapter的实现类,在getServletMappings()方法中配置Spring MVC需要拦截的请求路径正则表达式。而在WebMvcConfigurerAdapter实现类中,就可以定义我们平时在mvc-dispatcher.xml文件中配置的一些bean对象。要启动Spring Security还需要自己实现一个AbstractSecurityWebApplicationInitializer的子类,如果是基于Spring MVC的项目,就可以空实现,否则就需要在空构造方法中调用父类的构造方法,传入自己的Spring Security配置类,也就是一个继承于WebSecurityConfigurerAdapter自定义类,而自定义的WebSecurityConfigurerAdapter的子类,就相当于spring-security.xml文件,根据spring-security.xml文件中的配置相应地在configure(HttpSecurity
htt)方法中对http进行配置即可。而WebSecurityConfigurerAdapter.configureGlobal(AuthenticationManagerBuilder auth)方法中,就可以配置Spring Security用户的信息数据源。在这里,调用auth.userDetailsService(new CustomeUserDetailService());这样运行项目发现在提交验证表单后老是提示404,也就是没有处理器可以处理提交的用户信息表单,不能进行验证,这也是卡住我大半天的问题,在stack
overflow终于找到了解决方法,在这时需要给formLogin添加loginProcessingUrl,在基于xml配置Spring Security的时候就没出现这个问题,现在还是很迷。

    Sprng MVC启动器,看名字就可以知道,具体是如何做到自定义类就可以实现Spring MVC启动的呢?
package security.controller;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class MvcWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { WebConfig.class };
}

@Override
protected Class<?>[] getServletConfigClasses() {
// TODO Auto-generated method stub
return null;
}

@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}

}
看了下源码,AbstractAnnotationConfigDispatcherServletInitializer类继承于AbstractDispatcherServletInitializer,在AbstractDispatcherServletInitializer类中有一个protected void registerDispatcherServlet(ServletContext servletContext)方法,通过这个方法,可以拿到servlet框架的上下文,而这个方法的实现如下
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() may not return empty or null");

WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext,
"createServletApplicationContext() did not return an application " +
"context for servlet [" + servletName + "]");

DispatcherServlet dispatcherServlet = new DispatcherServlet(servletAppContext);
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
Assert.notNull(registration,
"Failed to register servlet with name '" + servletName + "'." +
"Check if there is another servlet registered under the same name.");

registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());

Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}

customizeRegistration(registration);
可以看到里面new了一个DispatcherServlet对象,这是Spring MVC提供的,基于xml配置的Spring MVC经常用到这个类,在web.xml中配置的,也就是说Spring MVC的拦截的路径的请求会被这个类的对象接收。再看DispatcherServlet是继承于FrameworkServlet的,而FrameworkServlet是继承于HttpServletBean,最后HttpServletBean又是继承于HttpServlet,OK,明白了,Spring MVC的内部原理就是通过自定义一个Servlet,让这个Servlet拦截多个请求,然后通过Spring MVC内部的分发Mapper映射到不同的Controller。这样看来Spring和Spring MC也并不神奇。这些都是基于Servlet3的特性。如果开发环境不支持Servlet3的话,是不能用自定义Spring MVC启动器来自动启动Spring MVC的。

再看Spring MVC的配置类的具体实现
package security.controller;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "security.controller" })
@Import(SecurityConfig.class)
public class WebConfig extends WebMvcConfigurerAdapter {

@Bean
public InternalResourceViewResolver resolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setViewClass(JstlView.class);
resolver.setPrefix("/WEB-INF/pages/");
resolver.setSuffix(".jsp");
return resolver;
}

}
   类名上面的一些注解也很好理解,表示是个配置类,支持WebMVC,组件扫描包"security.controller",等效于mvc-dispatcher.xml文件中的<context:component-scan base-package="security.controller" />。而下
c4cb
面的resolver()方法,是个工厂方法模式,在这个方法上面添加了@Bean注解,表示这个方法生产一个InternalResourceViewResolver对象,这个方法的实现相当于在mvc-dispatcher.xml文件中配置的
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/pages/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
因为Spring配置xml文件配置的模式,在Spring上下文启动的时候就会去解析相应的xml配置文件,然后将对应类路径上的对象生成出来,然后进行相应的对象之间的关系处理,这也是Spring底层实现模式。在WebConfig类名上面添加的@Import("SecurityConfig.class)注解表示将Spring Security的配置信息也添加到Spring MVC的上下文中,这是因为这个项目是把Spring MVC和Spirng Security进行整合,Spring会管理Spring Security的对象和请求映射。

再然后就是正题Spring Security的配置类
package security.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(new CustomeUserDetailService());
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/dba**").access("hasRole('ROLE_DBA')").antMatchers("/user**")
.access("hasRole('ROLE_USER')").and().formLogin().loginProcessingUrl("/j_spring_security_check").loginPage("/login").failureUrl("/login?error")
.usernameParameter("username").passwordParameter("password")
.successHandler(new AuthoritySuccessHandler()).and().csrf().and().logout()
.logoutSuccessUrl("/login?logout");
}

}
这个类跟spring-security.xml配置文件等效。由configure(HttpSecurity http)方法就很容易看出来。添加loginProcessingUrl("/j_spring_security_check),就是这一点,坑了我好久。

再然后就是Spring Security的配置器
package security.controller;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

public class WebSecurityInitializer extends AbstractSecurityWebApplicationInitializer {

}
因为此项目是基于Spring MVC,所以就可以空实现了。查看源码可以发现AbstractSecurityWebApplicationInitializer实现了WebApplicationInitializer接口,在AbstractSecurityWebApplicationInitializer的实现中,实现WebApplicationInitializer的onStartup方法时,向ServletContext中添加了一个org.springframework.security.web.session.HttpSessionEventPublisher,这个类实现了HttpSessionListener监听器,熟悉JavaWeb开发的都知道这个监听器可以监听HttpSession的生命周期,当Session创建时,也就是客户端第一次请求时会创建会话对象,也就是HttpSession对象,这时突发了HttpSession创建事件,监听器就会执行sessionCreated(HttpSessionEvent event)方法,在这方法中,可以发现添加了getContext(event.getSession().getServletContext()).publishEvent(e);实现,可以想象,在这个调用底层,一定就执行了Spring的启动过程。到这里,也已经弄完了Spring、Spring
MVC、Spring Security自启动的原理。在基于xml文件配置中,Spring Security是基于Servlet过滤器的,再看AbstractSecurityWebApplicationInitializer的onStartup方法的实现,在倒数第二行,调用了insertSpringSecurityFilterChain(servletContext);方法,再看insertSpringSecurityFilterChain(servletContext);方法的实现,发现创建了DelegatingFilterProxy的对象,看到这个类也会觉得非常熟悉,这个类就是在web.xml文件中配置的Spring Security的过滤器Filter。在方法的最后一行,调用了registerFilter(servletContext,
true, filterName, springSecurityFilterChain);方法,可以想到,在这个方法的实现中,一定是将这个DelegatingFilterProxy对象动态添加到了ServletContext上下文中,
private final void registerFilter(ServletContext servletContext, boolean insertBeforeOtherFilters, String filterName, Filter filter) {
Dynamic registration = servletContext.addFilter(filterName, filter);
if(registration == null) {
throw new IllegalStateException("Duplicate Filter registration for '" + filterName +"'. Check to ensure the Filter is only configured once.");
}
registration.setAsyncSupported(isAsyncSecuritySupported());
EnumSet<DispatcherType> dispatcherTypes = getSecurityDispatcherTypes();
registration.addMappingForUrlPatterns(dispatcherTypes, !insertBeforeOtherFilters, "/*");
}
而且过滤器拦截的路径也是/*,这和在web.xml文件中配置的一模一样。

         这样整个项目就配置成功了,运行也没有任何问题,达到了和用xml文件配置一样的效果。整个项目没有任何xml文件配置。web.xml文件还是eclipse创建项目时自动生成的,没有做丝毫修改。在此不得不感叹Spring生态系统设计得是多么神奇和优秀,虽然看了源码后觉得并没有什么神奇的,但必须得佩服Spring框架的设计,将如果复杂的过程简化到如此。
       再来基于java注解配置的好处,配置完后发现每个类的作用都用注解说明了,这样每个类都具有自描述性,这对于一个项目工程的开发是极其重要的,如果一个项目有100甚至更多的xml配置文件,那将是多久可怕的一种局面,不敢想象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: