Spring Security实战实用

以前也没有接触过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"?>
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">

       
       <security:http entry-point-ref="loginUrlAuthenticationEntryPoint" use-expressions="true">
              
              <security:intercept-url pattern="/succ/*" access="hasRole('ROLE_MOBILE_CUSTOMER')"/>



              
              <security:session-management session-fixation-protection="newSession"/>
              
              
              <security:custom-filter ref="simpleAuthenticationFilter" position="PRE_AUTH_FILTER" />

       security:http>
       id="loginUrlAuthenticationEntryPoint" class="com.ct10000.sc.sctelwap.wap.sso.entrypoint.WAPLoginUrlAuthenticationEntryPoint">
              
              index="0"  value="/succ/ssosuccessed"/>
       
       
       id="simpleAuthenticationFilter" class="com.ct10000.sc.sctelwap.wap.sso.filter.SimpleAuthenticationFilter">
              name="authenticationManager" ref="authenticationManager"/>
              name="authenticationSuccessHandler" ref="authenticationSuccessHandler"/>
              name="authenticationFailureHandler" ref="authenticationFailureHandler"/>
              name="authenticationDetailsSource" ref="authenticationDetailsSource"/>
       
       
       id="authenticationSuccessHandler" class="com.ct10000.sc.sctelwap.wap.sso.handler.WAPAuthenticationSuccessHandler"/>
       
       id="authenticationFailureHandler" class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
              
              name="defaultFailureUrl" value="/error/ssofailed"/>
       
       
       id="simpleAuthenticationProvider" class="com.ct10000.sc.sctelwap.wap.sso.provider.SimpleAuthenticationProvider"/>
        
       id="authenticationDetailsSource" class="com.ct10000.sc.sctelwap.wap.sso.source.WAPAuthenticationDetailsSource"/>


       <security:authentication-manager alias="authenticationManager">
              <security:authentication-provider ref="simpleAuthenticationProvider"/>
       security:authentication-manager>
3.web.xml配置



    deviceResolverRequestFilter
    org.springframework.mobile.device.DeviceResolverRequestFilter


    deviceResolverRequestFilter
    /*



    springSecurityFilterChain
    org.springframework.web.filter.DelegatingFilterProxy


    springSecurityFilterChain
    /*
4此处贴上Spring security的运行机制;

Spring Security实战实用_第1张图片

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 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 {

    /**
     * 日志对象
     */
    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> 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> verificationRequest(String channelAlias) {
    Map,String> outputParamMap = null;
    String endpointAddress="";
    //根据渠道别名查询渠道信息
    List, 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, 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中,也可以通过上面我提供的方法的从上下文中获取用户资料




你可能感兴趣的:(Spring)