技术标签: Spring
以前也没有接触过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用户信息认证 */ @Service public 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
@Override public 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中,也可以通过上面我提供的方法的从上下文中获取用户资料
使用国产化平台和软件(这里略过)统信UOS操作系统 wps国产化软件安装python内容安装好统信操作系统后,安装python编译器命令如下: sudo apt-get install python(如安装则不用安装) sudo apt-get install python3(如安装则不用安装)注意:统信系统必须开启开发者模式来进行安装。安装好python软件后查看python版本号注意:在统信操作系统中有2个版本:1) 2.7.16 2) 3.7....
1、API注册https://www.themoviedb.org/,打开改中文语言,然后注册—验证邮箱—登录2、API申请 应用简介,样板:Data is at the heart of every industry’s transformation, and this is where Synology has a profoundly important role to play. At its very core, our mission is to manage a.
前言这个是我要介绍的第三种轮播图—3d轮播图,这种轮播图在一些app里面会用得比较多,视觉效果很不错!!代码<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>网易云音乐轮播图实现</title> <link rel="stylesheet" href="./css/iconfont.css"> <link r
调用 matplotlib 模块时,出现:ImportError: No module named '_tkinter', please install the python3-tk package意思是没有该模块,找了一些博客,大多都是建议直接pip3 install python3-tk,在pycharm 终端 pip3 install python3-tk,出现以下问题:解决办法...
要使用MonkeyRunner,就要学习使用Python,哎先抄一段官方doc里的代码作用是启动一个程序(应该是启动程序默认的Activity),然后按MENU键,并截屏[code="python"]# Imports the monkeyrunner modules used by this programfrom com.android.monkeyrunner imp...
注:先找到 自己安装的 MathType 和 WPS 的安装目录最简单的方式就找到快捷方式查看 目标。以我为例我的安装软件是32位1、进入 MathType 目录在 MathPage 和 Office Support目录下找到对应 位数的文件:MathPage.wll 和 MathType Commands 2013.dotm 尤其是在找 MathType Commands 2013.dotm 文件要注意文件格式,因为在该目录下由于其同名文件2、配置wps 将步骤一中提取
Run a Command as Administrator from the Windows 7 / Vista Run box<br />If you are a command line junkie like me, and have been testing out Windows 7 or Vista… one of the first things you’ll notice is that there is no way to run a command from the run box i
①HashMap的工作原理HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会
umi.js使用方法node环境安装在官网下载与系统相应的node版本,node.js版本>=8.10编辑器推荐使用Visual Studio Code 安装方法安装uminpm install -g umi推荐使用 yarn 代替 npm 来安装 umi , yarn 会针对部分场景做一些缓存以节省时间,你可以输入以下命令来全局安装 yarn,使用yarn后项目中尽量避免再使用npm,不然...
WPS for Linux的主界面的右上角,有3个非常、非常、非常小的图标,这里藏有玄机!1在ElementaryOS上使用WPS for Linux的时间不长,但是体验还是相当不错的。客户端本身体积比较小,功能也很齐全,而且即使是免费账户也默认拥有1个GB的云端文档存储空间。这个1个GB的云端文档存储空间,相当于是一块文档同步网盘,它可以即时地将对云端文档的修改保存在云端,并同步到其它平台的客户..._1671465600
报错如下:/u01/app/11.2.0/grid/bin/clscfg.bin: error while loading shared libraries: libcap.so.1: cannot open shared object file: No such file or directory[[email protected] ~]# /u01/app/oraInventory
Sampler是一个用于shell命令执行,可视化和告警的工具。其配置使用的是一个简单的YAML文件。1、为什么我需要它?你可以直接从终端对任意动态进程进行采样 – 观察数据库中的更改,监控MQ动态消息(in-flight messages),触发部署脚本并在完成后获取通知。如果有一种方法可以使用shell命令获取指标(metric),那么可以使用Sampler立即对其进行可视化。2、安装macO...