Spring Security实战实用
来源:互联网 发布:野火软件官网 编辑:程序博客网 时间:2024/04/29 22:35
以前也没有接触过Spring Security,最近公司要重构一个安全登录控制,顺便学习了一下此框架,我会将我在学习过程中遇到的疑惑一一告诉大家,希望对大家有一些帮助,废话不多说直接上代码才是大家最关心的:
1.第一步得搭建Spring Security环境噻;
我使用的开发环境是IDEA:
springSecurity='3.2.9.RELEASE'springMobile='1.1.5.RELEASE'
"org.springframework.security:spring-security-web:$springSecurity","org.springframework.security:spring-security-config:$springSecurity","org.springframework.security:spring-security-remoting:$springSecurity","org.springframework.security:spring-security-acl:$springSecurity","org.springframework.security:spring-security-aspects:$springSecurity","org.springframework.security:spring-security-crypto:$springSecurity","org.springframework.security:spring-security-ldap:$springSecurity","org.springframework.security:spring-security-taglibs:$springSecurity","org.springframework.mobile:spring-mobile-device:$springMobile",当然也许要Spring的基础jar,这基础环境就大家自己去搭建了
2.编写Spring-security.xml文件,这个是关键,直接上代码
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="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-4.1.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd"> <!--WAP登录表单地址认证切入点--> <security:http entry-point-ref="loginUrlAuthenticationEntryPoint" use-expressions="true"> <!--配置访问地址必须具备的角色,"/succ/*"拦截所有成功地址--> <security:intercept-url pattern="/succ/*" access="hasRole('ROLE_MOBILE_CUSTOMER')"/> <!--session管理--> <security:session-management session-fixation-protection="newSession"/> <!-- <security:logout logout-url="/logout" logout-success-url="/login/randomcode"/> --> <!--配置过滤器--> <security:custom-filter ref="simpleAuthenticationFilter" position="PRE_AUTH_FILTER" /> </security:http> <bean id="loginUrlAuthenticationEntryPoint" class="com.ct10000.sc.sctelwap.wap.sso.entrypoint.WAPLoginUrlAuthenticationEntryPoint"> <!--构造方法参数--> <constructor-arg index="0" value="/succ/ssosuccessed"/> </bean> <!--SSO过滤器--> <bean id="simpleAuthenticationFilter" class="com.ct10000.sc.sctelwap.wap.sso.filter.SimpleAuthenticationFilter"> <property name="authenticationManager" ref="authenticationManager"/> <property name="authenticationSuccessHandler" ref="authenticationSuccessHandler"/> <property name="authenticationFailureHandler" ref="authenticationFailureHandler"/> <property name="authenticationDetailsSource" ref="authenticationDetailsSource"/> </bean> <!--验证成功处理器--> <bean id="authenticationSuccessHandler" class="com.ct10000.sc.sctelwap.wap.sso.handler.WAPAuthenticationSuccessHandler"/> <!--验证失败处理器--> <bean id="authenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"> <!--sso认证失败跳转页面--> <property name="defaultFailureUrl" value="/error/ssofailed"/> </bean> <!--验证细节Provider--> <bean id="simpleAuthenticationProvider" class="com.ct10000.sc.sctelwap.wap.sso.provider.SimpleAuthenticationProvider"/> <!--WAP 认证细节源对象--> <bean id="authenticationDetailsSource" class="com.ct10000.sc.sctelwap.wap.sso.source.WAPAuthenticationDetailsSource"/> <security:authentication-manager alias="authenticationManager"> <security:authentication-provider ref="simpleAuthenticationProvider"/> </security:authentication-manager></beans>3.web.xml配置
<!--源设备过滤器--><filter> <filter-name>deviceResolverRequestFilter</filter-name> <filter-class>org.springframework.mobile.device.DeviceResolverRequestFilter</filter-class></filter><filter-mapping> <filter-name>deviceResolverRequestFilter</filter-name> <url-pattern>/*</url-pattern></filter-mapping><!--Spring Security--><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>4此处贴上Spring security的运行机制;
SimpleAuthenticationProvider细节验证:
执行SimpleAuthenticationUserDetailsService 方法查询客户资料
loadUserDetails()中执行逻辑:
1.POST请求认证接口,返回数据包括用户号码,号码类型,渠道ID;
2.根据用户号码,号码类型,渠道ID查询客户资料,返回MobileUser;
验证成功之后Spring Security会自动把用户信息保存到上下文中,获取用户信息
user = (MobileUser)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
5.关键代码来了
Filter:
/** * Created by leitao on 2016/12/21. * 单点登陆过滤器 */public class SimpleAuthenticationFilter extends AbstractAuthenticationProcessingFilter { /** * 日志对象 */ private Logger log = LogManager.getLogger(getClass()); /** * 单点登陆过滤url */ public static final String SSO_AUTHENTICATION_FILTER_PROCESSES_URL = "/ssologin/index.html"; /** * 如果参数传递渠道为空,则默认此渠道 */ public static final String DEFAULT_CHANNEL = "xyzd"; /** * 创建新的实例 */ public SimpleAuthenticationFilter() { this(SSO_AUTHENTICATION_FILTER_PROCESSES_URL); } protected SimpleAuthenticationFilter(String defaultFilterProcessesUrl) { super(defaultFilterProcessesUrl); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { log.info("进入SimpleAuthenticationFilter"); //参数验证 String params = request.getParameter("params");//入参 String channel = request.getParameter("channel");//渠道 log.info("入参params:" + params + ",渠道channel:" + channel); UsernamePasswordAuthenticationToken token = null; Authentication authentication=null; if (StringUtils.isEmpty(params)) { redirectRequest(request, response); }else { //判断渠道是否为空 if (StringUtils.isEmpty(channel)) { channel = DEFAULT_CHANNEL; } token = new UsernamePasswordAuthenticationToken(params, channel); //设置其他认证信息 setDetail(request, token); //进入provider处理链,进行用户信息详细认证 authentication = this.getAuthenticationManager().authenticate(token); //验证之后的用户对象 MobileUser mobiuser= (MobileUser) authentication.getPrincipal(); //跳转到要单点的url String url = mobiuser.getUrl(); if(!StringUtils.isEmpty(url)){ log.info("将单点跳转地址存入session:"+url); redirectUrl(request,url); } } return authentication; } /** * 登录参数错误重定向请求 * * @param request 请求对象 * @param response 响应对象 * @throws IOException */ private void redirectRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { RedirectUrlBuilder builder = new RedirectUrlBuilder(); builder.setScheme("http"); builder.setServerName(request.getServerName()); builder.setPort(request.getServerPort()); builder.setContextPath(request.getContextPath()); builder.setPathInfo("/login/error"); log.info("request url:" + builder.getUrl()); response.sendRedirect(builder.getUrl()); } /** * 将要单点的地址存入session * @param request * @param url */ private void redirectUrl(HttpServletRequest request, String url) { RedirectUrlBuilder builder = new RedirectUrlBuilder(); builder.setScheme("http"); builder.setServerName(request.getServerName()); builder.setPort(request.getServerPort()); builder.setContextPath(request.getContextPath()); builder.setPathInfo(url); log.info("request url:" + builder.getUrl()); //将需要跳转的url存入session,SpringSecurity验证成功后SuccessHandler从Session中取出url跳转 request.getSession().setAttribute("returnUrl", builder.getUrl()); } /** * 设置额外的认证信息,如ip,设备来源...等 * * @param request * @param authRequest */ private void setDetail(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) { authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); }}Provider:
/** * Created by leitao on 2016/12/21. * SSO认证. */public class SimpleAuthenticationProvider implements AuthenticationProvider, InitializingBean, Ordered { /** * 日志对象 */ private Logger log = LogManager.getLogger(getClass()); @Autowired private AuthenticationUserDetailsService<UsernamePasswordAuthenticationToken> simpleAuthenticationUserDetailsService; private int order = -1; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { log.info("进入SimpleAuthenticationProvider:"+authentication); if (!supports(authentication.getClass())){ return null; } if (!(authentication.getPrincipal() instanceof String)){ return null; } //加载用户详细信息 UserDetails userDetails = simpleAuthenticationUserDetailsService.loadUserDetails((UsernamePasswordAuthenticationToken) authentication); log.info("userDetails:"+userDetails); UsernamePasswordAuthenticationToken resultToken = new UsernamePasswordAuthenticationToken(userDetails,userDetails.getPassword(),userDetails.getAuthorities()); resultToken.setDetails(authentication.getDetails()); return resultToken; } /** * 判断传入的对象是否是UsernamePasswordAuthenticationToken,是就执行authenticate方法,不是就执行下一个Provider * @param authentication * @return */ @Override public boolean supports(Class<?> authentication) { return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); } @Override public void afterPropertiesSet() throws Exception { Assert.notNull(simpleAuthenticationUserDetailsService, "An AuthenticationUserDetailsService must be set"); } @Override public int getOrder() { return order; }}
SimpleAuthenticationUserDetailsService:
/** * Created by leitao on 2016/12/21. * SSO用户信息认证 */@Servicepublic class SimpleAuthenticationUserDetailsService implements AuthenticationUserDetailsService<UsernamePasswordAuthenticationToken> { /** * 日志对象 */ Logger log = LogManager.getLogger(getClass()); /** * SSO 请求认证 */ @Autowired private ISimpleAuthService simpleAuthService; /** * SSO 日志 */ @Autowired private ISsoLogService ssoLogService; @Override public UserDetails loadUserDetails(UsernamePasswordAuthenticationToken token) throws UsernameNotFoundException { log.info("进入SimpleAuthenticationUserDetailsService:"+token); MobileUser user = null; try { String channelAlias = token.getCredentials().toString(); log.info("渠道别名channelAlias:"+channelAlias); //单点认证 Map<String,String> map =simpleAuthService.verificationRequest(channelAlias); if (null ==map || map.size()==0){ log.info("单点请求认证失败,可能没有此渠道"); throw new VerificationRequestErrorException(); } String number = map.get("Number");//电话号码 String acrType = map.get("NumberType");//号码类型 String code = map.get("Citycode");//区号 String channel_id = map.get("ChannelID");//渠道ID String url = map.get("Url");//单点访问地址 log.info("单点认证出参电话号码Number:"+number+",号码类型NumberType:"+acrType+",区号Citycode:"+code+",单点访问地址:"+url); //通过登录号码从CRM查询用户注册信息,acrType 号码类型(9手机,固话1,宽带2) log.info("根据号码从CRM查询客户资料."); CustInfo custInfo = CrmTool.qryCustInfo(number, acrType, code); //如果没有查到相应的用户信息则抛此异常 if (null == custInfo){ log.info("没有找到此用户:"+number); throw new RegisterDataNotFoundException(); }else { log.info("用户姓名:" + custInfo.getCustName()); CustomerDetail customerDetail = new CustomerDetail(); customerDetail.setCustInfo(custInfo); user = new MobileUser(number, token.getCredentials().toString(), MobileUserAuthority.getAuthorities(customerDetail), customerDetail, channel_id,url); ssoLogService.saveSsoLog(number,acrType,code,channel_id,url,((WAPAuthenticationDetails) token.getDetails()).getRemoteAddress()); } }catch (VerificationRequestErrorException e){ throw new UsernameNotFoundException(e.getMessage(), e.getCause()); }catch (RegisterDataNotFoundException e){ throw new UsernameNotFoundException(e.getMessage(), e.getCause()); } return user; }}
ssoLogService
@Overridepublic Map<String, String> verificationRequest(String channelAlias) { Map<String,String> outputParamMap = null; String endpointAddress=""; //根据渠道别名查询渠道信息 List<Map<String, Object>> maps = ssoChannelInfoDao.selectSSOChannelInfoByChannelAlias(channelAlias); if (null!=maps && maps.size()>0){ SSOChannelInfo ssoChannelInfo = convertMap2SSOChannelInfo(maps.get(0)); if (null!=ssoChannelInfo){ endpointAddress = ssoChannelInfo.getEndpointAddress(); log.info("终端验证地址endpointAddress:"+endpointAddress); //输入参数 String inputParam = ParseSsoParam.getInputParams(ssoChannelInfo); //调用单点验证接口输出参数 String outputParam = HttpRequest.sendPost(endpointAddress,inputParam); log.info("调用单点验证接口输出参数:"+outputParam); outputParamMap=ParseSsoParam.getOutputParams(ssoChannelInfo,outputParam); log.info("调用单点验证接口输出Map解析参数"+outputParamMap); } } return outputParamMap;}
WAPAuthenticationDetails
/** * Created by leitao on 2016/12/22. * WAP认证细节对象 */public class WAPAuthenticationDetails implements Serializable { private static final int HASH_CODE = 7654; private static final int SEVEN = 7; private String remoteAddress; private String sessionId; /** * 设备来源 */ private DeviceOrigin deviceOrigin; /** * * @return IP地址 */ public String getRemoteAddress() { return remoteAddress; } public void setRemoteAddress(String remoteAddress) { this.remoteAddress = remoteAddress; } public String getSessionId() { return sessionId; } public void setSessionId(String sessionId) { this.sessionId = sessionId; } public DeviceOrigin getDeviceOrigin() { return deviceOrigin; } public void setDeviceOrigin(DeviceOrigin deviceOrigin) { this.deviceOrigin = deviceOrigin; } public WAPAuthenticationDetails(){ } /** * 创建新实例 * @param request */ public WAPAuthenticationDetails(HttpServletRequest request) { HttpSession session = request.getSession(false); this.sessionId = (session != null) ? session.getId() : null; obtainRemoteAddress(request); obtainDeviceOrigin(request); } private void obtainRemoteAddress(HttpServletRequest request) { remoteAddress = request.getHeader("x-forwarded-for"); if (remoteAddress == null || remoteAddress.length() == 0 || "unknown".equalsIgnoreCase(remoteAddress)) { remoteAddress = request.getHeader("Proxy-Client-IP"); } if (remoteAddress == null || remoteAddress.length() == 0 || "unknown".equalsIgnoreCase(remoteAddress)) { remoteAddress = request.getHeader("WL-Proxy-Client-IP"); } if (remoteAddress == null || remoteAddress.length() == 0 || "unknown".equalsIgnoreCase(remoteAddress)) { remoteAddress = IPGetTool.getIpAddr(request); } } /** * 获取设备来源. * 客户端设备识别:识别结果只有3种类型:NORMAL(非手机设备)、MOBILE(手机设备)、TABLET(平板电脑)。在系统里可以通过以下代码获取设备识别结果: * 网站偏好设置:Spring 通过设备识别的结果来设置当前网站是NORMAL还是MOBILE。 * 最后 Spring Mobile会将信息同时放入cookie和request attribute里面。 * 网站自动切换:可根据不同的访问设备切换到对应的页面 * 此处需要在web.xml中配置DeviceResolverRequestFilter过滤器 * @param request 请求对象 */ private void obtainDeviceOrigin(HttpServletRequest request) { Device currentDevice = DeviceUtils.getCurrentDevice(request); if (currentDevice.isMobile()) { this.deviceOrigin = DeviceOrigin.MOBILE; } if (currentDevice.isNormal()) { this.deviceOrigin = DeviceOrigin.NORMAL; } if (currentDevice.isTablet()) { this.deviceOrigin = DeviceOrigin.TABLET; } } /** * 比较对象的IP地址. * * @param rhs 被比较的对象 * @return 布尔值 */ public boolean isEqualsRemoteAddress(WAPAuthenticationDetails rhs) { if ((remoteAddress == null) && (rhs.getRemoteAddress() != null)) { return false; } if ((remoteAddress != null) && (rhs.getRemoteAddress() == null)) { return false; } if (remoteAddress != null && !remoteAddress.equals(rhs.getRemoteAddress())) { return false; } return true; } /** * 比较对象的会话ID. * * @param rhs 被比较的对象 * @return 布尔值 */ public boolean isEqualsSessionId(WAPAuthenticationDetails rhs) { if ((sessionId == null) && (rhs.getSessionId() != null)) { return false; } if ((sessionId != null) && (rhs.getSessionId() == null)) { return false; } if (sessionId != null && !sessionId.equals(rhs.getSessionId())) { return false; } return true; } @Override public boolean equals(Object obj) { if (obj instanceof WAPAuthenticationDetails) { WAPAuthenticationDetails rhs = (WAPAuthenticationDetails) obj; if (isEqualsRemoteAddress(rhs) && isEqualsSessionId(rhs)) { return true; } } return false; } @Override public int hashCode() { int code = HASH_CODE; if (this.remoteAddress != null) { code = code * (this.remoteAddress.hashCode() % SEVEN); } if (this.sessionId != null) { code = code * (this.sessionId.hashCode() % SEVEN); } return code; }}
WAPAuthenticationDetailsSource
/** * Created by leitao on 2016/12/22. * WAP 认证细节源对象 */public class WAPAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, WAPAuthenticationDetails> { @Override public WAPAuthenticationDetails buildDetails(HttpServletRequest context) { return new WAPAuthenticationDetails(context); }}
登录成功后获取用户资料:
/** * Created by leitao on 2016/12/28. * Wap上下文工具 */public class WapContextUtils { /** * 获取WAP登录用户(也就是当前会话中的用户). * @return 用户信息 */ public static MobileUser getMobileUser() { MobileUser user = null; if (SecurityContextHolder.getContext().getAuthentication().getPrincipal() instanceof MobileUser) { user = (MobileUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); } return user; }}特别提醒用户bean一定要集成Spring Security提供的User类,这样Spring Security才会帮我们管理该对象;
切入点:
/** * Created by leitao on 2016/12/21. * WAP登录表单地址认证切入点 */public class WAPLoginUrlAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint { /** * 创建新的实例 * @param loginFormUrl 登录表单地址 */ public WAPLoginUrlAuthenticationEntryPoint(String loginFormUrl){ super(loginFormUrl); }}单点登陆成功后:
/** * Created by leitao on 2016/12/21. * SSO WAP认证成功处理器 */public class WAPAuthenticationSuccessHandler implements AuthenticationSuccessHandler { /** * 日志对象 */ Logger log = LogManager.getLogger(getClass()); @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { log.info("进入WAP认证成功处理器"); RedirectUrlBuilder builder = new RedirectUrlBuilder(); builder.setScheme("http"); builder.setServerName(request.getServerName()); builder.setPort(request.getServerPort()); builder.setContextPath(request.getContextPath()); String returnUrl = builder.getUrl(); Object object = request.getSession().getAttribute("returnUrl"); if (!StringUtils.isEmpty(object)){ returnUrl = object.toString(); request.getSession().removeAttribute("returnUrl"); } if (!StringUtils.isEmpty(returnUrl)){ log.info("认证成功后跳转地址returnUrl:"+returnUrl); response.sendRedirect(returnUrl); return; }else { request.getRequestDispatcher("/").forward(request,response); } }}跳转到相应的地址;
此时Spring Security会把SPRING_SECURITY_CONTEXT 存入Session中,也可以通过上面我提供的方法的从上下文中获取用户资料
1 0
- Spring Security实战实用
- Spring Security实战
- 【Spring Security实战系列】Spring Security实战(一)
- 【Spring Security实战系列】Spring Security实战(二)
- 【Spring Security实战系列】Spring Security实战(三)
- 【Spring Security实战系列】Spring Security实战(四)
- 【Spring Security实战系列】Spring Security实战(五)
- 【Spring Security实战系列】Spring Security实战(六)
- 【Spring Security实战系列】Spring Security实战(七)
- Spring实战4之Spring Security
- 基于注释的Spring Security实战指南
- 基于注释的Spring Security实战指南
- Spring security实战(1)-----项目搭建
- Spring security实战(2)-----搭建SpringBoot
- Spring MVC 4 + Spring Security 4 + Hibernate +JPA实战
- spring实战-Spring-security实现用户权限认证登录
- spring实战-Spring-security权限认证白名单
- Spring 3.x企业实用开发实战
- telnet使用技巧
- 存储过程实现递归算法
- Yarn 资源调度策略
- 浅谈iOS中私有成员变量和属性的选用
- 在ndk中尝试使用原生线程
- Spring Security实战实用
- Java-Web微信网页支付开发流程以及各种坑
- jquery插件tablesorter自动排序
- BZOJ2427: [HAOI2010]软件安装
- 一元N次方程,转为字符串在oracle用power函数计算
- raw 文件读取
- 39级台阶问题
- R语言学习二
- java8 求年、月、周的第几天