cas实现单点登录(2)

cas实现单点登录过程(二)

上次说过了cas的实现整个流程,现在说一下cas的代码实现方式。
我这里使用的shiro+cas的方式进行实现的,生成token的方式,这里是一个事例具体得要根据你自己的项目进行改造。
也可以根据我的cas实现过程修改后使用在spring-security中
前提是你本地要搭建cas server,我本地已经搭建完成。
直接上代码
添加shiro与cas的maven依赖

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-cas</artifactId>
    <version>1.4.0</version>
</dependency>

ShiroConfig的配置

/**
 * @description: shiro配置类
 */
@Configuration
public class ShiroConfiguration {

	@Autowired
	private JwtTokenUtil jwtTokenUtil;

	/**
	 * Shiro Realm 继承自AuthorizingRealm的自定义Realm,即指定Shiro验证用户登录的类为自定义的
	 */
	@Bean(name = "userRealm")
	public UserRealm userRealm() {
		UserRealm userRealm = new UserRealm();
		userRealm.setCachingEnabled(false);
		return userRealm;
	}

	@Bean
	public FilterRegistrationBean delegatingFilterProxy() {
		FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
		DelegatingFilterProxy proxy = new DelegatingFilterProxy();
		proxy.setTargetFilterLifecycle(true);
		proxy.setTargetBeanName("shiroFilter");
		filterRegistrationBean.setFilter(proxy);
		return filterRegistrationBean;
	}

	/**
	 * Shiro的Web过滤器Factory 命名:shiroFilter
	 */
	@Bean(name = "shiroFilter")
	public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
		//Shiro的核心安全接口,这个属性是必须的
		shiroFilter.setLoginUrl("/cas/login");
		shiroFilter.setSecurityManager(securityManager);
		Map<String, Filter> filters = new LinkedHashMap<>();
		CasCallbackFilter casCallbackFilter = new CasCallbackFilter();
		casCallbackFilter.setCasServerUrlPrefix("https://localhost:8443/cas-server-webapp-tomcat-5.3.14");
		casCallbackFilter.setCasService("http://localhost:8999/cas/callback");
		casCallbackFilter.setJwtTokenUtil(jwtTokenUtil);
		filters.put("casCallback", casCallbackFilter);
		JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter();
		jwtAuthenticationFilter.setRedirectUrl("https://localhost:8443/cas-server-webapp-tomcat-5.3.14/login?service=http://localhost:8999/cas/callback");
		filters.put("authc", jwtAuthenticationFilter);

		shiroFilter.setFilters(filters);
		/*定义shiro过滤链  Map结构
		 * Map中key(xml中是指value值)的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的
		 * anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种
		 * authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter
		 */
		Map<String, String> filterMap = new LinkedHashMap<>();
         /* 过滤链定义,从上向下顺序执行,一般将 / ** 放在最为下边:这是一个坑呢,一不小心代码就不好使了;
          authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问 */
		filterMap.put("/", "anon");
		filterMap.put("/static/**", "anon");
		filterMap.put("/login/auth", "anon");
		filterMap.put("/login/logout", "anon");
		filterMap.put("/error", "anon");

		filterMap.put("/cas/callback", "casCallback");
		filterMap.put("/**", "authc");

		shiroFilter.setFilterChainDefinitionMap(filterMap);
		return shiroFilter;
	}

	/**
	 * Subject工厂管理器
	 * @return
	 */
	@Bean
	public DefaultWebSubjectFactory subjectFactory(){
		DefaultWebSubjectFactory subjectFactory = new StatelessDefaultSubjectFactory();
		return subjectFactory;
	}

	/**
	 * 不指定名字的话,自动创建一个方法名第一个字母小写的bean
	 */
	@Bean
	public SecurityManager securityManager() {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		securityManager.setRealm(userRealm());
		// 替换默认的DefaultSubjectFactory,用于关闭session功能
		securityManager.setSubjectFactory(subjectFactory());
		securityManager.setSessionManager(sessionManager());
		// 关闭session存储,禁用Session作为存储策略的实现,但它没有完全地禁用Session所以需要配合SubjectFactory中的context.setSessionCreationEnabled(false)
		((DefaultSessionStorageEvaluator) ((DefaultSubjectDAO)securityManager.getSubjectDAO()).getSessionStorageEvaluator()).setSessionStorageEnabled(false);
		SecurityUtils.setSecurityManager(securityManager);
		return securityManager;
	}

	/**
	 * 会话管理器
	 * @return
	 */
	public DefaultSessionManager sessionManager(){
		DefaultSessionManager sessionManager =new DefaultSessionManager();
		// 关闭session定时检查,通过setSessionValidationSchedulerEnabled禁用掉会话调度器
		sessionManager.setSessionValidationSchedulerEnabled(false);
		return  sessionManager;
	}

	/**
	 * 用户授权信息缓存
	 * @return
	 */
	@Bean
	public CacheManager cacheManager() {
		return new MemoryConstrainedCacheManager();
	}

	/**
	 * 凭证匹配器
	 * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
	 * 所以我们需要修改下doGetAuthenticationInfo中的代码;
	 * )
	 * 可以扩展凭证匹配器,实现 输入密码错误次数后锁定等功能,下一次
	 */
	@Bean
	public CredentialsMatcher CredentialsMatcher() {
		HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
		//散列算法:这里使用MD5算法;
		hashedCredentialsMatcher.setHashAlgorithmName("MD5");
		//散列的次数,比如散列两次,相当于 md5(md5(""));
		hashedCredentialsMatcher.setHashIterations(1024);
		//storedCredentialsHexEncoded默认是true,此时用的是密码加密用的是Hex编码;false时用Base64编码
		hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
		return hashedCredentialsMatcher;
	}

	/**
	 * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
	 * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
	 */
	@Bean
	public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
		DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
		advisorAutoProxyCreator.setProxyTargetClass(true);
		return advisorAutoProxyCreator;
	}

	@Bean
	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
		AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
		authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
		return authorizationAttributeSourceAdvisor;
	}
}

JwtAuthenticationFilter 执行URL的转发跳转到cas server中认证 拿取token做

public class JwtAuthenticationFilter extends AuthenticatingFilter {

    private final Logger LOGGER = LoggerFactory.getLogger(getClass());

    private String redirectUrl;

    public void setRedirectUrl(String redirectUrl) {
        this.redirectUrl = redirectUrl;
    }

    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse servletResponse) throws Exception {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        // 先从Header里面获取   前端将token放置在header中
        String token = httpRequest.getHeader(Constant.TOKEN);
        if(StringUtils.isEmpty(token)){
            // 获取不到再从Parameter中拿
            token = httpRequest.getParameter(Constant.TOKEN);
            // 还是获取不到再从Cookie中拿
            if(StringUtils.isEmpty(token)){
                Cookie[] cookies = httpRequest.getCookies();
                if(cookies != null){
                    for (Cookie cookie : cookies) {
                        if(Constant.TOKEN.equals(cookie.getName())){
                            token = cookie.getValue();
                            break;
                        }
                    }
                }
            }
        }
        JwtToken jwtToken = new JwtToken();
        jwtToken.setToken(token);
        return jwtToken;
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        if (isLoginRequest(request,response)) {
            // TODO 跳转到认证服务 cas server带上回调地址  便于登录认证成功后回调到自己的服务
            ((HttpServletResponse) response).sendRedirect("https://localhost:8443/cas-server-webapp-tomcat-5.3.14/login?service=http://localhost:8999/cas/callback");
        }
        return super.executeLogin(request, response);
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        //必须设置要不不能得到token  因为这里是自动以的header所以要进行设置
        if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) {
            return true;
        }
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest)request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        // 获取请求token,如果token不存在,直接返回401
        String token = getRequestToken((HttpServletRequest) request);
        if (StringUtils.isBlank(token)) {
            return executeLogin(request, response);
        }
        try {

            return executeLogin(request, response);
        } catch (Exception e) {
           e.printStackTrace();
            return false;
        }
    }


    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
                                     ServletResponse response) throws Exception {
        return true;
    }

    /**
     * 获取请求的token
     */
    private String getRequestToken(HttpServletRequest httpRequest) {
        // 从header中获取token
        String token = httpRequest.getHeader("token");
        // 如果header中不存在token,则从参数中获取token
        if (StringUtils.isBlank(token)) {
            token = httpRequest.getParameter("token");
        }
        if (StringUtils.isBlank(token)) {
            token = httpRequest.getParameter("sx_sso_sessionid");
        }
        return token;
    }
}

StatelessDefaultSubjectFactory

/**
 *  通过调用context.setSessionCreationEnabled(false)表示不创建会话,
 *  如果之后调用Subject.getSession()将抛出DisabledSessionException异常。
 */
public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {

    @Override
    public Subject createSubject(SubjectContext context) {
        // 不创建session
        context.setSessionCreationEnabled(false);
        return super.createSubject(context);
    }
}

CasCallbackFilter 从cas server中拿取ticket并验证是否正确过期,并生成token,cas server登出销毁token

public class CasCallbackFilter extends PathMatchingFilter {

    private JwtTokenUtil jwtTokenUtil;

    private TicketValidator ticketValidator;

    private String casService;

    private String casServerUrlPrefix;

    public String getCasServerUrlPrefix() {
        return casServerUrlPrefix;
    }

    public void setCasServerUrlPrefix(String casServerUrlPrefix) {
        this.casServerUrlPrefix = casServerUrlPrefix;
    }

    public String getCasService() {
        return casService;
    }

    public void setCasService(String casService) {
        this.casService = casService;
    }

    public JwtTokenUtil getJwtTokenUtil() {
        return jwtTokenUtil;
    }

    public void setJwtTokenUtil(JwtTokenUtil jwtTokenUtil) {
        this.jwtTokenUtil = jwtTokenUtil;
    }

    public CasCallbackFilter() {
        setSuffix(Constant.DEFAULT_CALLBACK_SUFFIX);
    }

    protected TicketValidator ensureTicketValidator() {
        if (this.ticketValidator == null) {
            this.ticketValidator = createTicketValidator();
        }
        return this.ticketValidator;
    }

    protected TicketValidator createTicketValidator() {
        String urlPrefix = getCasServerUrlPrefix();
        return new Cas20ServiceTicketValidator(urlPrefix);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        if (mustApply(servletRequest)){
            HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
            String ticket = "";
            //判断ticket是否为空
            if (isTokenRequest(httpRequest)){
                ticket = httpRequest.getParameter(Constant.TICKET_NAME);
                Assertion casAssertion = null;
                try {
                    ticketValidator = ensureTicketValidator();
                    casAssertion = ticketValidator.validate(ticket, getCasService());
                    // get principal, user id and attributes
                    AttributePrincipal casPrincipal = casAssertion.getPrincipal();
                    String username = casPrincipal.getName();

                    // 验证用户名密码成功后生成token
                    String token = jwtTokenUtil.generateToken(username);
                    //TODO 保存ticket与token

                    //跳转到前端服务并携带token
                    ((HttpServletResponse) servletResponse).sendRedirect("http://localhost:9520/#/?token=" + token);
                } catch (TicketValidationException e) {
                    e.printStackTrace();
                }
            } else if (isBackLogoutRequest(httpRequest)) {
                //得到登出标识
                final String logoutMessage = httpRequest.getParameter(Constant.LOGOUT_NAME);
                //解析得到ticket
                ticket = substring(logoutMessage,"SessionIndex>","</");
                //TODO 在数据库中删除ticket
            }
        }else {
            filterChain.doFilter(servletRequest, servletResponse);
        }
    }

    @Override
    public void destroy() {

    }

    private boolean isBackLogoutRequest(HttpServletRequest httpRequest) {
        if (StringUtils.equalsAnyIgnoreCase(HttpMethod.POST.name(), httpRequest.getMethod())){
            String contentType = "";
            Enumeration<String> headerNames = httpRequest.getHeaderNames();
            while ( headerNames.hasMoreElements()){
                String headerName = headerNames.nextElement();
                if (StringUtils.equalsAnyIgnoreCase(headerName,Constant.CONTENT_TYPE_NAME)) {
                    contentType = httpRequest.getHeader(headerName);
                    if ( !StringUtils.startsWithIgnoreCase(contentType,Constant.TICKET_MULTIPART)){
                        if (StringUtils.isNoneEmpty(httpRequest.getParameter(Constant.LOGOUT_NAME))){
                            return Boolean.TRUE;
                        }
                    }
                }
            }

        }
        return Boolean.FALSE;
    }

    public static String substring(final String str, final String open, final String close) {
        if (str == null || open == null || close == null) {
            return null;
        }
        int start = str.indexOf(open);
        if (start != Constant.INDEX_NOT_FOUND) {
            int end = str.indexOf(close, start + open.length());
            if (end != Constant.INDEX_NOT_FOUND) {
                return str.substring(start + open.length(), end);
            }
        }
        return null;
    }

    private boolean isTokenRequest(HttpServletRequest httpRequest) {
        return StringUtils.isNoneEmpty(httpRequest.getParameter(Constant.TICKET_NAME));
    }
}

PathMatchingFilter

public abstract class PathMatchingFilter implements Filter {

    protected final Logger logger = LoggerFactory.getLogger(getClass());

    private String suffix;

    protected boolean mustApply(final ServletRequest request) {
        final String path = getPath((HttpServletRequest)request);
        logger.debug("path: {} | suffix: {}", path, suffix);
        if (StringUtils.isEmpty(suffix)) {
            return true;
        } else {
            return path != null && path.endsWith(suffix);//判断后缀是否相等  相等返回true
        }
    }

    public String getPath(HttpServletRequest request) {
        final String fullPath = request.getRequestURI();
        // it shouldn't be null, but in case it is, it's better to return empty string
        if (fullPath == null) {
            return "";
        }
        final String context = request.getContextPath();
        // this one shouldn't be null either, but in case it is, then let's consider it is empty
        if (context != null) {
            return fullPath.substring(context.length());
        }
        return fullPath;
    }

    public String getSuffix() {
        return suffix;
    }

    public void setSuffix(final String suffix) {
        this.suffix = suffix;
    }

}

JwtToken

public class JwtToken implements AuthenticationToken {

    private String principal;

    private String token;

    @Override
    public String getPrincipal() {
        return principal;
    }

    @Override
    public Object getCredentials() {
        return token;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public JwtToken() {
    }

    public JwtToken(String principal, String token) {
        this.principal = principal;
        this.token = token;
    }
}
/**
 * @description: 自定义Realm
 */
public class UserRealm extends AuthorizingRealm {

	private Logger logger = LoggerFactory.getLogger(UserRealm.class);

	@Autowired
	private JwtTokenUtil jwtTokenUtil;

	@Autowired
	private LoginService loginService;

	@Override
	public boolean supports(AuthenticationToken token) {
		//表示此Realm只支持JwtToken类型
		return token instanceof JwtToken;
	}

	@Override
	@SuppressWarnings("unchecked")
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		// 根据用户名查找角色,请根据需求实现
		String username = (String)principals.getPrimaryPrincipal();

		Session session = SecurityUtils.getSubject().getSession();
		//查询用户的权限
		JSONObject permission = (JSONObject) session.getAttribute(Constant.SESSION_USER_PERMISSION);
		logger.info("permission的值为:" + permission);
		logger.info("本用户权限为:" + permission.get("permissionList"));
		//为当前用户设置角色和权限
		SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
		authorizationInfo.addStringPermissions((Collection<String>) permission.get("permissionList"));
		return authorizationInfo;
	}

	/**
	 * 验证当前登录的Subject
	 * LoginController.login()方法中执行Subject.login()时 执行此方法
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
		JwtToken jwtToken = (JwtToken) authcToken;
		// 获取token
		String token = jwtToken.getToken();
		String username = jwtTokenUtil.getUsernameFromToken(token);

		// 获取用户密码
		//String password = new String((char[]) authcToken.getCredentials());
		JSONObject user = loginService.getUser(username);
		if (user == null) {
			//没找到帐号
			throw new UnknownAccountException();
		}
		//交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现
		SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
				user.getString("username"),
				token,
				//user.getString("password"),
				//ByteSource.Util.bytes("salt"), salt=username+salt,采用明文访问时,不需要此句
				getName()
		);
		//session中不需要保存密码
		//user.remove("password");
		//将用户信息放入session中
		SecurityUtils.getSubject().getSession().setAttribute(Constant.SESSION_USER_INFO, user);
		return authenticationInfo;
	}

	@Override
	public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
		super.clearCachedAuthorizationInfo(principals);
	}

	@Override
	public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
		super.clearCachedAuthenticationInfo(principals);
	}

	@Override
	public void clearCache(PrincipalCollection principals) {
		super.clearCache(principals);
	}

	public void clearAllCachedAuthorizationInfo() {
		getAuthorizationCache().clear();
	}

	public void clearAllCachedAuthenticationInfo() {
		getAuthenticationCache().clear();
	}

	public void clearAllCache() {
		clearAllCachedAuthenticationInfo();
		clearAllCachedAuthorizationInfo();
	}
}
@Component
public class JwtTokenUtil {

    @Value("${jwt.token.secret}")
    private String secret;

    @Value("${jwt.token.expiration}")
    private Long expiration;

    public String getSecret() {
        return secret;
    }

    public void setSecret(String secret) {
        this.secret = secret;
    }

    public Long getExpiration() {
        return expiration;
    }

    public void setExpiration(Long expiration) {
        this.expiration = expiration;
    }

    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }


    /**
     * 生成token
     * @param username 用户名
     * @param
     * @return
     */
    public String generateToken(String username) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(Constant.CLAIM_KEY_USERNAME, username);
//        claims.put(CLAIM_KEY_AUDIENCE, generateAudience(device));
        claims.put(Constant.CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }

    private String generateToken(Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationDate())
                .signWith(SignatureAlgorithm.HS512, this.secret)
                .compact();
    }

    /**
     * 生成token时间 = 当前时间 + expiration(properties中配置的失效时间)
     * @return
     */
    private Date generateExpirationDate() {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

//    /**
//     * 通过spring-mobile-device的device检测访问主体
//     * @param device
//     * @return
//     */
//    private String generateAudience(Device device) {
//        String audience = AUDIENCE_UNKNOWN;
//        if (device.isNormal()) {
//            audience = AUDIENCE_WEB;//PC端
//        } else if (device.isTablet()) {
//            audience = AUDIENCE_TABLET;//平板
//        } else if (device.isMobile()) {
//            audience = AUDIENCE_MOBILE;//手机
//        }
//        return audience;
//    }

    /**
     * 根据token获取用户名
     * @param token
     * @return
     */
    public String getUsernameFromToken(String token) {
        String username;
        try {
            final Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 判断token失效时间是否到了
     * @param token
     * @return
     */
    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    /**
     * 获取设置的token失效时间
     * @param token
     * @return
     */
    public Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }

    // /**
    //  * Token失效校验
    //  * @param token token字符串
    //  * @param loginInfo 用户信息
    //  * @return
    //  */
    // public Boolean validateToken(String token, LoginInfo loginInfo) {
    //     final String username = getUsernameFromToken(token);
    //     return (
    //             username.equals(loginInfo.getUsername())
    //                     && !isTokenExpired(token));
    // }

    public String refreshToken(String token) {
        final Claims claims = this.getClaimsFromToken(token);
        claims.put(Constant.CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }
}
public class Constant {

    public static final String SUCCESS_CODE = "100";
    public static final String SUCCESS_MSG = "请求成功";

    /**
     * session中存放用户信息的key值
     */
    public static final String SESSION_USER_INFO = "userInfo";
    public static final String SESSION_USER_PERMISSION = "userPermission";


    /*------------------------------------------------cas--------------------------------------*/

    public static final String CLAIM_KEY_USERNAME = "sub";

    public static final String CLAIM_KEY_AUDIENCE = "audience";

    public static final String CLAIM_KEY_CREATED = "created";

    public static final String AUDIENCE_UNKNOWN = "unknown";

    public static final String AUDIENCE_WEB = "web";

    public static final String AUDIENCE_MOBILE = "mobile";

    public static final String AUDIENCE_TABLET = "tablet";

    public static final String TOKEN = "token";

    public final static String DEFAULT_CALLBACK_SUFFIX = "/callback";

    public static final String TICKET_NAME = "ticket";

    public static final String TICKET_MULTIPART = "multipart";

    public static final String CONTENT_TYPE_NAME = "Content-Type";

    public static final String LOGOUT_NAME = "logoutRequest";

    public static final int INDEX_NOT_FOUND = -1;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值