SpringMVC + security模块 框架整合详解
来源:互联网 发布:优化人员结构 编辑:程序博客网 时间:2024/06/05 18:22
1、首先是springmvc部分,也是框架的最核心骨架部分。springmvc也是基于servlet框架完成的,所以我们都可以从web.xml入手,然后顺藤摸瓜,整理出整个架构。web.xml中主要部分是放置spring的各种配置:
<context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/spring/appServlet/servlet-context.xml </param-value> </context-param>
和
<servlet> <servlet-name>appServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
这两部分指定了配置文件地址和web请求地址拦截规则。需要注意的是,然后配置文件里就是常用的注解、数据源、之类的,不一而足。
2、然后是security,本文实现了自定义的用户信息登录认证,主要部分:spring的security主要注意几个地方:
1)web.xml中配置security必须的过滤器,注意名字必须是springSecurityFilterChain,这是内置的过滤器;然后将过滤规则设置为/*,表示对所有请求都拦截。
2) 关于security的配置文件必须在<context-param>中加入,因为ContextLoaderListener在加载的时候就会去加载security相关的东西,而此时springmvc(servlet)模块还没有初始化。
二者配置如下:
<context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/spring/appServlet/servlet-context.xml </param-value> </context-param> <!-- Creates the Spring Container shared by all Servlets and Filters --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener><!-- 用户权限模块 --> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>本文中security配置文件再servlet-context.xml中被引入,形如:
<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。
<?xml version="1.0" encoding="UTF-8"?><beans:beans xmlns="http://www.springframework.org/schema/security"xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:security="http://www.springframework.org/schema/security"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd"><!-- For Web security --><http pattern="/js/**" security="none"/> <http pattern="/images/**" security="none"/> <http pattern="/mgmt/**" security="none"/> <http pattern="/image_sys/**" security="none"/> <http pattern="/style/**" security="none"/> <http pattern="/view/login.jsp" security="none"/> <http pattern="/view/login/forgotPassword.jsp" security="none"/> <http pattern="/securityCodeImage.html" security="none"/> <http pattern="/checkSecurityCode.html" security="none"/> <http pattern="/admin/validateMobile.html" security="none"/> <http pattern="/verifycode/sendVerifyCode.html" security="none"/> <http pattern="/admin/resetPassword.html" security="none"/> <http use-expressions="true" entry-point-ref="authenticationProcessingFilterEntryPoint" access-denied-page="/view/403.jsp"> <intercept-url pattern="/**" access="isAuthenticated()" /> <remember-me /> <!-- <expression-handler ref="webSecurityExpressionHandler"/> --> <custom-filter ref="loginFilter" position="FORM_LOGIN_FILTER" /> <logout logout-url="/j_spring_security_logout" logout-success-url="/view/login.jsp" delete-cookies="JSESSIONID"/> </http> <!-- 未登录的切入点 --> <beans:bean id="authenticationProcessingFilterEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"> <beans:property name="loginFormUrl" value="/view/login.jsp"></beans:property> </beans:bean> <!-- 登录体系loginFilter --> <beans:bean id="loginFilter" class="com.security.AdminUsernamePasswordAuthenticationFilter"> <beans:property name="filterProcessesUrl" value="/j_spring_security_check"></beans:property> <beans:property name="authenticationSuccessHandler" ref="myAuthenticationSuccessHandler"></beans:property> <beans:property name="authenticationFailureHandler" ref="myAuthenticationFailureHandler"></beans:property> <beans:property name="authenticationManager" ref="authenticationManager"></beans:property> </beans:bean> <!-- 验证失败显示页面 --> <beans:bean id="myAuthenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"> <beans:property name="defaultFailureUrl" value="/view/login.jsp?error=true" /> </beans:bean><!-- 验证成功默认显示页面 --> <beans:bean id="myAuthenticationSuccessHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler"> <beans:property name="alwaysUseDefaultTargetUrl" value="true" /> <!--此处可以请求登录首页的action地址 --> <beans:property name="defaultTargetUrl" value="/index/homepage" /> </beans:bean> <!-- authentication体系 --> <authentication-manager alias="authenticationManager" erase-credentials="false"> <authentication-provider ref="authenticationProvider" /> </authentication-manager> <beans:bean id="authenticationProvider" class="com.security.AdminAuthenticationProvider"> <beans:property name="userDetailsService" ref="userDetailsService" /> </beans:bean> <beans:bean id="userDetailsService" class="com.security.AdminUserDetailsService"> <beans:property name="adminService" ref="adminServiceImpl"></beans:property> </beans:bean>
</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模块认证后的用户完整信息。
/** * 用户登录验证步骤一:attemptAutentication() */public class AdminUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter { public static final String VALIDATE_CODE = "validateCode"; public static final String USERNAME = "j_username"; public static final String PASSWORD = "j_password"; public static final String EMPLOYEENO = "j_employeeNo"; @Resource private AdminService adminService; @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (!request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } // 判断用户信息 // UsernamePasswordAuthenticationToken实现 Authentication UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( adminModel.getAdminId(), password); // 允许子类设置详细属性 setDetails(request, authRequest); // 运行UserDetailsService的loadUserByUsername 再次封装Authentication return this.getAuthenticationManager().authenticate(authRequest); } @Override protected String obtainUsername(HttpServletRequest request) { Object obj = request.getParameter(USERNAME); return null == obj ? "" : obj.toString(); } @Override protected String obtainPassword(HttpServletRequest request) { Object obj = request.getParameter(PASSWORD); return null == obj ? "" : obj.toString(); }}
/** * <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;"></span> */public class AdminAuthenticationProvider implements AuthenticationProvider { protected Logger logger = LoggerFactory.getLogger(this.getClass()); private UserDetailsService userDetailsService = null; public AdminAuthenticationProvider() { super(); } /** * @param userDetailsService */ public AdminAuthenticationProvider(UserDetailsService userDetailsService) { super(); this.userDetailsService = userDetailsService; } public UserDetailsService getUserDetailsService() { return userDetailsService; } public void setUserDetailsService(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } /** * provider的authenticate()方法,用于登录验证 */ @SuppressWarnings("unchecked") public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 1. Check username and password try { doLogin(authentication); } catch (Exception e) { if (e instanceof AuthenticationException) { throw (AuthenticationException) e; } logger.error("failure to doLogin", e); } // 2. Get UserDetails UserDetails userDetails = null; try { userDetails = this.userDetailsService.loadUserByUsername(authentication.getName()); } catch (Exception e) { if (e instanceof AuthenticationException) { throw (AuthenticationException) e; } logger.error("failure to get user detail", e); }
// 3. Check and get all of admin roles and contexts. Collection<GrantedAuthority> authorities = (Collection<GrantedAuthority>) userDetails.getAuthorities(); if (authorities != null && !authorities.isEmpty()) { AdminAuthenticationToken token = new AdminAuthenticationToken(authentication.getName(), authentication.getCredentials(), authorities); token.setDetails(userDetails); return token; } throw new BadCredentialsException("没有分配权限"); } protected void doLogin(Authentication authentication) throws AuthenticationException { } @Override public boolean supports(Class<?> authentication) { // TODO Auto-generated method stub return true; }}
/**用户详细信息获取 */public class AdminUserDetailsService implements UserDetailsService { private Logger logger = LoggerFactory.getLogger(AdminUserDetailsService.class); private AdminService adminService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { AdminUserDetails userDetails = new AdminUserDetails(); userDetails.setAdminId(username); ///加载用户基本信息 AdminModel adminModel = adminService.getAdminByAdminId(username); try { PropertyUtils.copyProperties(userDetails, adminModel); } catch (IllegalAccessException e) { logger.error("用户信息复制到userDetails出错",e); } catch (InvocationTargetException e) { logger.error("用户信息复制到userDetails出错",e); } catch (NoSuchMethodException e) { logger.error("用户信息复制到userDetails出错",e); } //加载权限信息 List<AdminRoleGrantedAuthority> authorities = this.adminService.getAuthorityByUserId(username); if (authorities == null || authorities.size() == 0) {////如果为普通用户 if (isCommonUserRequest()) { AdminRoleGrantedAuthority authority = new AdminRoleGrantedAuthority(AdminRoleGrantedAuthority.ADMIN_ROLE_TYPE_COMMON_USER); userDetails.getAuthorities().add(authority); } else { logger.warn("person authorities is empty, personId is [{}]", username); } } //加载用户权限 userDetails.getAuthorities().addAll(authorities); ///这个就是权限系统最后的用户信息 return userDetails; } private boolean isCommonUserRequest() { // TODO Auto-generated method stub return true; } public AdminService getAdminService() { return adminService; } public void setAdminService(AdminService adminService) { this.adminService = adminService; }}
public class AdminAuthenticationToken extends AbstractAuthenticationToken { /** * */ private static final long serialVersionUID = 5976309306377973996L; private final Object principal; private Object credentials; public AdminAuthenticationToken(Object principal, Object credentials) { super(null); this.principal = principal; this.credentials = credentials; setAuthenticated(false); }c AdminAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.principal = principal; this.credentials = credentials; super.setAuthenticated(true); //注意,这里设置为true了! must use super, as we override } public Object getCredentials() { return this.credentials; } public Object getPrincipal() { return this.principal; } public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { if (isAuthenticated) { throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead"); } super.setAuthenticated(false); } @Override public void eraseCredentials() { super.eraseCredentials(); credentials = null; }}
到这里基本就完成了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/
- SpringMVC + security模块 框架整合详解
- SpringMVC + security模块 框架整合详解
- SpringMVC + security模块 框架整合详解
- mybats +springmvc +spring4 框架整合详解
- springboot+security框架整合
- springmvc+hibernate+security整合笔记
- springMVC整合velocity框架
- springMVC整合velocity框架
- springMVC+ibatis 框架整合
- SpringMVC整合Swagger框架
- springMVC+mybatis 框架整合
- SpringMVC整合Tiles框架
- springMVC+Hibernate 框架整合
- SpringMVC+Mybatis框架整合
- SpringMVC整合Tiles框架
- SpringMVC+Hibernate框架整合
- 框架整合----springMVC
- SpringMVC+MyBatis框架整合
- LDA 线性判别分析(二)
- Android apk动态加载机制的研究
- 手把手教你使用git GitHub创建管理仓库
- iOS 添加 framework 报缺少头文件
- DCT变换
- SpringMVC + security模块 框架整合详解
- pip: 修改源地址
- jquery ajax jsonp 跨域访问
- Java:单例模式的七种写法
- new的实现原理
- 购物车设计思路
- 国外第三方分享,包括Facebook,twitter,Instagram
- itext
- 2016-1-7-Thingking in Java 读书笔记(四)---控制执行流程