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