SpringMVC + security模块 框架整合详解

来源:互联网 发布:减肥晚上吃什么 知乎 编辑:程序博客网 时间:2024/05/22 01:34
 最近整理一个二手后台管理项目,整体大体可分为 : Springmvc + mybatis + tiles + easyui + security 这几个模块,再用maven做管理。刚拿到手里面东西实在太多,所以老大让重新整一个,只挑出骨架。不可否认,架构整体规划得还是很好的,尤其是前端界面用tiles+easyui ,开发很高效!其实,之前虽然听说过security,但做过的项目都没用过security,所以也算是一个新手!整合过程还是很烧脑的。

    1、首先是springmvc部分,也是框架的最核心骨架部分。springmvc也是基于servlet框架完成的,所以我们都可以从web.xml入手,然后顺藤摸瓜,整理出整个架构。web.xml中主要部分是放置spring的各种配置:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <context-param>  
  2.         <param-name>contextConfigLocation</param-name>  
  3.         <param-value>  
  4.         /WEB-INF/spring/appServlet/servlet-context.xml  
  5.         </param-value>  
  6.           
  7.     </context-param>  

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <servlet>  
  2.         <servlet-name>appServlet</servlet-name>  
  3.         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
  4.         <init-param>  
  5.             <param-name>contextConfigLocation</param-name>  
  6.             <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>  
  7.         </init-param>  
  8.         <load-on-startup>1</load-on-startup>  
  9.         <async-supported>true</async-supported>  
  10.     </servlet>  
  11.       
  12.     <servlet-mapping>  
  13.         <servlet-name>appServlet</servlet-name>  
  14.         <url-pattern>/</url-pattern>  
  15.     </servlet-mapping>  



这两部分指定了配置文件地址和web请求地址拦截规则。需要注意的是,然后配置文件里就是常用的注解、数据源、之类的,不一而足。


    2、然后是security,本文实现了自定义的用户信息登录认证,主要部分:spring的security主要注意几个地方:

       1)web.xml中配置security必须的过滤器,注意名字必须是springSecurityFilterChain,这是内置的过滤器;然后将过滤规则设置为/*,表示对所有请求都拦截。

       2)  关于security的配置文件必须在<context-param>中加入,因为ContextLoaderListener在加载的时候就会去加载security相关的东西,而此时springmvc(servlet)模块还没有初始化。

      二者配置如下:

       

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <context-param>  
  2.         <param-name>contextConfigLocation</param-name>  
  3.         <param-value>  
  4.         /WEB-INF/spring/appServlet/servlet-context.xml  
  5.         </param-value>  
  6.           
  7.     </context-param>  
  8.   
  9.     <!-- Creates the Spring Container shared by all Servlets and Filters -->  
  10.     <listener>  
  11.         <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
  12.     </listener>  
  13.   
  14. <!-- 用户权限模块 -->  
  15.     <filter>  
  16.         <filter-name>springSecurityFilterChain</filter-name>  
  17.         <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
  18.     </filter>  
  19.     <filter-mapping>  
  20.         <filter-name>springSecurityFilterChain</filter-name>  
  21.         <url-pattern>/*</url-pattern>  
  22.     </filter-mapping>  
本文中security配置文件再servlet-context.xml中被引入,形如:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <beans:import resource="spring-security.xml"/>  

     3)spring-security.xml配置文件:

          现在来看这个xml文件:

          首先 <http>标签部分声明了访问拦截规则,这是security的关键。在这部分,我们先声明了一些不需要验证的资源和访问路径;然后是地址拦截规则部分:该部分拦截路径是/**,**表示可以跨目录结构,因此此处拦截该站点所有请求(除之前声明security=“none”的以外),然后拦截规则access="isAuthenticated()",这是SecurityExpressionRoot中的判断是否认证过的方法之一,跟hasRole()类似,官方描述是Returns true if the user is not anonymous ,也就是用户认证后返回true。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans:beans xmlns="http://www.springframework.org/schema/security"  
  3.     xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.     xmlns:security="http://www.springframework.org/schema/security"  
  5.     xsi:schemaLocation="http://www.springframework.org/schema/beans   
  6.                         http://www.springframework.org/schema/beans/spring-beans-3.1.xsd  
  7.                         http://www.springframework.org/schema/security   
  8.                         http://www.springframework.org/schema/security/spring-security-3.2.xsd">  
  9.       
  10.     <!-- For Web security -->  
  11.     <http pattern="/js/**" security="none"/>    
  12.     <http pattern="/images/**" security="none"/>  
  13.     <http pattern="/mgmt/**" security="none"/>  
  14.     <http pattern="/image_sys/**" security="none"/>  
  15.     <http pattern="/style/**" security="none"/>  
  16.     <http pattern="/view/login.jsp" security="none"/>  
  17.     <http pattern="/view/login/forgotPassword.jsp" security="none"/>  
  18.     <http pattern="/securityCodeImage.html" security="none"/>  
  19.     <http pattern="/checkSecurityCode.html" security="none"/>  
  20.     <http pattern="/admin/validateMobile.html" security="none"/>  
  21.     <http pattern="/verifycode/sendVerifyCode.html" security="none"/>  
  22.     <http pattern="/admin/resetPassword.html" security="none"/>  
  23.       
  24.           
  25.     <http use-expressions="true" entry-point-ref="authenticationProcessingFilterEntryPoint" access-denied-page="/view/403.jsp">  
  26.         <intercept-url pattern="/**" access="isAuthenticated()" />  
  27.         <remember-me />  
  28.        <!--  <expression-handler ref="webSecurityExpressionHandler"/> -->  
  29.         <custom-filter ref="loginFilter"  position="FORM_LOGIN_FILTER" />  
  30.         <logout logout-url="/j_spring_security_logout" logout-success-url="/view/login.jsp" delete-cookies="JSESSIONID"/>  
  31.     </http>  
  32.       
  33.     <!-- 未登录的切入点 -->    
  34.     <beans:bean id="authenticationProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">    
  35.         <beans:property name="loginFormUrl" value="/view/login.jsp"></beans:property>    
  36.     </beans:bean>   
  37.       
  38.     <!-- 登录体系loginFilter -->  
  39.   <beans:bean id="loginFilter"  
  40.         class="com.security.AdminUsernamePasswordAuthenticationFilter">  
  41.         <beans:property name="filterProcessesUrl" value="/j_spring_security_check"></beans:property>  
  42.         <beans:property name="authenticationSuccessHandler" ref="myAuthenticationSuccessHandler"></beans:property>  
  43.         <beans:property name="authenticationFailureHandler" ref="myAuthenticationFailureHandler"></beans:property>  
  44.         <beans:property name="authenticationManager" ref="authenticationManager"></beans:property>  
  45.     </beans:bean>  
  46.       
  47.     <!-- 验证失败显示页面 -->  
  48.     <beans:bean id="myAuthenticationFailureHandler"  
  49.         class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">  
  50.         <beans:property name="defaultFailureUrl" value="/view/login.jsp?error=true" />  
  51.     </beans:bean>  
  52. <!-- 验证成功默认显示页面  -->  
  53.     <beans:bean id="myAuthenticationSuccessHandler"  
  54.         class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">  
  55.         <beans:property name="alwaysUseDefaultTargetUrl" value="true" />  
  56.         <!--此处可以请求登录首页的action地址  -->  
  57.         <beans:property name="defaultTargetUrl" value="/index/homepage" />  
  58.     </beans:bean>  
  59.       
  60. <!-- authentication体系 -->  
  61.     <authentication-manager alias="authenticationManager" erase-credentials="false">  
  62.         <authentication-provider ref="authenticationProvider" />  
  63.     </authentication-manager>  
  64.     <beans:bean id="authenticationProvider"  
  65.         class="com.security.AdminAuthenticationProvider">  
  66.         <beans:property name="userDetailsService" ref="userDetailsService" />  
  67.     </beans:bean>  
  68.     <beans:bean id="userDetailsService"  
  69.         class="com.security.AdminUserDetailsService">  
  70.         <beans:property name="adminService" ref="adminServiceImpl"></beans:property>  
  71.     </beans:bean>  
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. </beans:beans>  

      然后声明了自定义的用户登录的拦截器loginFilter,用于在登录时实现security认证。spring的登录认证模块执行顺序为:

1、UsernamePasswordAuthenticationFilter的attemptAuthentication()方法,此部分可以做用户名密码的判断,正确后继续执行;

2、接下来调用AuthenticationManager的authenticate()方法(中途具体怎么调用此处不深究),一个Mannager对应了多个authenticationProvider,其实最终是通过调用provider的authenticate()方法来进行认证的,只要provider的supports方法返回true即可声明该provider或可进行认证,最后将被manager调用。在authenticate()方法中,调用UserDetails的loadUserByUserName()方法来加载登录用户信息,包括权限信息,最后封装成AbstractAuthenticationToken,这个对象就是security模块认证后的用户完整信息。

自定义认证主要类如下:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * 用户登录验证步骤一:attemptAutentication() 
  3.  */  
  4. public class AdminUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {  
  5.   
  6.     public static final String VALIDATE_CODE = "validateCode";  
  7.     public static final String USERNAME = "j_username";  
  8.     public static final String PASSWORD = "j_password";  
  9.     public static final String EMPLOYEENO = "j_employeeNo";  
  10.   
  11.     @Resource  
  12.     private AdminService adminService;  
  13.   
  14.     @Override  
  15.     public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)  
  16.             throws AuthenticationException {  
  17.         if (!request.getMethod().equals("POST")) {  
  18.             throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());  
  19.         }  
  20.   
  21.         // 判断用户信息  
  22.          
  23.         // UsernamePasswordAuthenticationToken实现 Authentication  
  24.         UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(  
  25.                 adminModel.getAdminId(), password);  
  26.   
  27.         // 允许子类设置详细属性  
  28.         setDetails(request, authRequest);  
  29.   
  30.         // 运行UserDetailsService的loadUserByUsername 再次封装Authentication  
  31.         return this.getAuthenticationManager().authenticate(authRequest);  
  32.     }  
  33.     @Override  
  34.     protected String obtainUsername(HttpServletRequest request) {  
  35.         Object obj = request.getParameter(USERNAME);  
  36.         return null == obj ? "" : obj.toString();  
  37.     }  
  38.     @Override  
  39.     protected String obtainPassword(HttpServletRequest request) {  
  40.         Object obj = request.getParameter(PASSWORD);  
  41.         return null == obj ? "" : obj.toString();  
  42.     }  
  43. }  

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * <span style="font-family: Arial, Helvetica, sans-serif;">用户登录验证步骤二:</span><span style="font-family: Arial, Helvetica, sans-serif;">authenticate</span><span style="font-family: Arial, Helvetica, sans-serif;">()</span><span style="font-family: Arial, Helvetica, sans-serif;"> 
  3. </span> */  
  4. public class AdminAuthenticationProvider implements AuthenticationProvider {  
  5.     protected Logger logger = LoggerFactory.getLogger(this.getClass());  
  6.     
  7.     private UserDetailsService userDetailsService = null;  
  8.   
  9.     public AdminAuthenticationProvider() {  
  10.         super();  
  11.     }  
  12.     /** 
  13.      * @param userDetailsService 
  14.      */  
  15.     public AdminAuthenticationProvider(UserDetailsService userDetailsService) {  
  16.         super();  
  17.         this.userDetailsService = userDetailsService;  
  18.     }  
  19.   
  20.     public UserDetailsService getUserDetailsService() {  
  21.         return userDetailsService;  
  22.     }  
  23.   
  24.     public void setUserDetailsService(UserDetailsService userDetailsService) {  
  25.         this.userDetailsService = userDetailsService;  
  26.     }  
  27.   
  28.     /** 
  29.      * provider的authenticate()方法,用于登录验证 
  30.      */  
  31.     @SuppressWarnings("unchecked")  
  32.     public Authentication authenticate(Authentication authentication) throws AuthenticationException {  
  33.   
  34.         // 1. Check username and password  
  35.         try {  
  36.             doLogin(authentication);  
  37.         } catch (Exception e) {  
  38.             if (e instanceof AuthenticationException) {  
  39.                 throw (AuthenticationException) e;  
  40.             }  
  41.             logger.error("failure to doLogin", e);  
  42.         }  
  43.   
  44.         // 2. Get UserDetails  
  45.         UserDetails userDetails = null;  
  46.         try {  
  47.             userDetails = this.userDetailsService.loadUserByUsername(authentication.getName());  
  48.         } catch (Exception e) {  
  49.             if (e instanceof AuthenticationException) {  
  50.                 throw (AuthenticationException) e;  
  51.             }  
  52.             logger.error("failure to get user detail", e);  
  53.         }  
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1.     // 3. Check and get all of admin roles and contexts.  
  2.     Collection<GrantedAuthority> authorities = (Collection<GrantedAuthority>) userDetails.getAuthorities();  
  3.     if (authorities != null && !authorities.isEmpty()) {  
  4.         AdminAuthenticationToken token = new AdminAuthenticationToken(authentication.getName(),  
  5.                 authentication.getCredentials(), authorities);  
  6.         token.setDetails(userDetails);  
  7.         return token;  
  8.     }  
  9.     throw new BadCredentialsException("没有分配权限");  
  10. }  
  11. protected void doLogin(Authentication authentication) throws AuthenticationException {  
  12.       
  13. }  
  14. @Override  
  15. public boolean supports(Class<?> authentication) {  
  16.     // TODO Auto-generated method stub  
  17.     return true;  
  18. }  

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /**用户详细信息获取 
  2.  */  
  3. public class AdminUserDetailsService implements UserDetailsService {  
  4.     private Logger logger = LoggerFactory.getLogger(AdminUserDetailsService.class);  
  5.     private AdminService adminService;  
  6.       
  7.     @Override  
  8.     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {  
  9.           
  10.         AdminUserDetails userDetails = new AdminUserDetails();  
  11.         userDetails.setAdminId(username);  
  12.           
  13.         ///加载用户基本信息  
  14.         AdminModel adminModel = adminService.getAdminByAdminId(username);  
  15.         try {  
  16.             PropertyUtils.copyProperties(userDetails, adminModel);  
  17.         } catch (IllegalAccessException e) {  
  18.             logger.error("用户信息复制到userDetails出错",e);  
  19.         } catch (InvocationTargetException e) {  
  20.             logger.error("用户信息复制到userDetails出错",e);  
  21.         } catch (NoSuchMethodException e) {  
  22.             logger.error("用户信息复制到userDetails出错",e);  
  23.         }  
  24.         //加载权限信息  
  25.         List<AdminRoleGrantedAuthority> authorities = this.adminService.getAuthorityByUserId(username);  
  26.         if (authorities == null || authorities.size() == 0) {////如果为普通用户  
  27.             if (isCommonUserRequest()) {  
  28.           AdminRoleGrantedAuthority authority =   
  29.                 new AdminRoleGrantedAuthority(AdminRoleGrantedAuthority.ADMIN_ROLE_TYPE_COMMON_USER);  
  30.           userDetails.getAuthorities().add(authority);  
  31.             } else {  
  32.                 logger.warn("person authorities is empty, personId is [{}]", username);  
  33.             }  
  34.         }  
  35.         //加载用户权限  
  36.         userDetails.getAuthorities().addAll(authorities);  
  37.           
  38.         ///这个就是权限系统最后的用户信息  
  39.         return userDetails;  
  40.     }  
  41.   
  42.     private boolean isCommonUserRequest() {  
  43.         // TODO Auto-generated method stub  
  44.         return true;  
  45.     }  
  46.   
  47.     public AdminService getAdminService() {  
  48.         return adminService;  
  49.     }  
  50.   
  51.     public void setAdminService(AdminService adminService) {  
  52.         this.adminService = adminService;  
  53.     }  
  54.   
  55. }  


[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class AdminAuthenticationToken extends AbstractAuthenticationToken {  
  2.    
  3.   /** 
  4.    *  
  5.    */  
  6.   private static final long serialVersionUID = 5976309306377973996L;  
  7.   
  8.   private final Object principal;  
  9.   private Object credentials;  
  10.   
  11.   public AdminAuthenticationToken(Object principal, Object credentials) {  
  12.     super(null);  
  13.     this.principal = principal;  
  14.     this.credentials = credentials;  
  15.     setAuthenticated(false);  
  16.   }  
  17.   
  18. c AdminAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {  
  19.     super(authorities);  
  20.     this.principal = principal;  
  21.     this.credentials = credentials;  
  22.     super.setAuthenticated(true); //注意,这里设置为true了! must use super, as we override  
  23.   }  
  24.   
  25.   public Object getCredentials() {  
  26.     return this.credentials;  
  27.   }  
  28.   
  29.   public Object getPrincipal() {  
  30.     return this.principal;  
  31.   }  
  32.   
  33.   public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {  
  34.     if (isAuthenticated) {  
  35.       throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");  
  36.     }  
  37.     super.setAuthenticated(false);  
  38.   }  
  39.   
  40.   @Override  
  41.   public void eraseCredentials() {  
  42.     super.eraseCredentials();  
  43.     credentials = null;  
  44.   }  
  45. }  

到这里基本就完成了spring security的设置,


         4)最后就是jsp页面部分,页面主要是表单的action地址必须为:path+/j_spring_security_check,其中path是项目路径,然后登录的时候就可以使用security模块了!

         至于文章中可能涉及到的一些bean或者service,这里就不贴代码了,毕竟项目较为完善,涉及东西太多,另外每个项目业务逻辑也不一样。


         在整个框架整合过程中,遇到了很多奇怪的问题,很多是因为版本不一致引起的,建议用maven进行jar等依赖包的统一管理。

        下面是spring security3.2的reference 手册:http://docs.spring.io/spring-security/site/docs/3.2.9.RELEASE/reference/htmlsingle/

0 0
原创粉丝点击