oauth2统一认证授权_authenticationsuccessevent oauth2.0 统一认证-程序员宅基地

技术标签: 笔记  

oauth2.0统一认证授权

1.认证与授权

​ 最近学习整理了下认证授权相关实现,下面是大概的一些理解与学习过程。在分布式系统中每个服务都需要认证,授权。如果每个服务都实现一套认证授权的逻辑就会显得冗余,考虑到分布式系统共享性的特点,我们可以独立一个授权服务出来,可以对内部系统或者第三方应用提供认证。

1.1统一认证授权:

​ 提供独立的认证服务,统一处理认证授权。不论是什么用户还是不同种类的客户端,例如小程序,APP,web,都采用一致的认证,权限,会话机制。同时保持开放性,可以接入第三方外部应用。

2.OAuth2.0协议

​ Oauth是一个开放标准协议,他允许用户授权第三方应用访问他们存储在服务提供者上的信息。不需要提供用户名密码等敏感信息,而只需要提供服务提供者允许且用户(客户端)需要的信息。

(1).授权码模式

​ 最常见的例子就是我们平时微信支付宝的第三方登录。用户在浏览器访问网站A.如果该网站接入了微信认证。那么他可以申请微信认证。微信发放授权码,令牌等一系列操作,然后网站A持有该令牌去获取微信用户的信息。

在这里插入图片描述
1、客户端请求第三方授权用户进入网站A的登录页面,点击微信的图标以微信账号登录系统,点击“微信”出现一个二维码,此时用户扫描二维码,开始给网站A授权。

2、用户同意给客户端授权资源拥有者扫描二维码表示资源拥有者同意给客户端授权,微信会对资源拥有者的身份进行验证, 验证通过后,微信会询问用户是否给授权网站A访问自己的微信数据,用户点击“确认登录”表示同意授权,微信认证服务器会颁发一个授权码,并重定向到网站A.

3、客户端获取到授权码,请求认证服务器申请令牌此过程用户看不到,客户端应用程序请求认证服务器,请求携带授权码。

4、认证服务器向客户端响应令牌微信认证服务器验证了客户端请求的授权码,如果合法则给客户端颁发令牌,令牌是客户端访问资源的通行证。此交互过程用户看不到,当客户端拿到令牌后,用户在网站A看到已经登录成功。

5、客户端请求资源服务器的资源客户端携带令牌访问资源服务器的资源。网站A携带令牌请求访问微信服务器获取用户的基本信息。

6、资源服务器返回受保护资源,资源服务器校验令牌的合法性,如果合法则向用户响应资源信息内容。

以上流程只是oauth2.0的一种模式,即授权码模式。也是最oauth2中最安全的一种模式。授权码模式是oauth2中最安全的一种模式。观察上面的流程可以发现token始终没有经过浏览器也没有暴露密码,一切授权操作都是交给客户端的后台进行。因此这种模式对于第三方应用接入应用适用。

(2)密码模式:

在这里插入图片描述
流程:
1.用户想访问客户端,直接提供用户名密码给客户端。
2.客户端将用户名,密码交给授权服务直接获取令牌。
密码模式相对于授权码模式简单一些,但没有授权码模式那么安全,因为他讲用户名密码暴露给了客户端。这种模式一般只适用于客户端是我们内部开发的情况,对于接入外部应用就不行了。

3.Spring Cloud Security OAuth2

​ spring-Security-Oauth2是springcloud对oauth2的实现,oauth2的服务提供了两个服务。授权服务(Authorization Server)与资源服务(Resource server)

3.1授权服务

​ 授权服务(UAA) (Authorization Server)应包含对接入端以及登入用户的合法性进行验证并颁发token等功能。

AuthorizationEndpoint 服务于认证请求。默认 URL: /oauth/authorize 。
TokenEndpoint 服务于访问令牌的请求。默认 URL: /oauth/token 。

3.1.1授权服务搭建

​ 在父工程下建立security-uaa授权服务。
在这里插入图片描述
创建授权服务配置类: 用@EnableAuthorizationServer注解并且实现AuthorizationServerConfigurerAdapte配置授权服务。

@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
    
}

AuthorizationServerConfigurerAdapter要求配置以下几个类:这几个对象由spring-oauth2进行管理,他们会被spring传入AuthorizationServerConfigurer中进行配置。

public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {
    
    public AuthorizationServerConfigurerAdapter() {
    
    }

    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    
    }

    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    
    }

    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    
    }
}

ClientDetailsServiceConfigurer:用来配置客户端详情服务,客户端的详细信息都在这里进行配置。客户端的信息在这里可以被写死,也可以将他们保存在数据库中。简单的来说这里就是来配置允许认证的客户端。

**AuthorizationServerEndpointsConfigurer:**用来配置令牌访问端点与令牌服务,申请令牌的url。令牌发放生成规则。

AuthorizationServerSecurityConfigurer:令牌的安全约束,访问令牌的安全约束。

3.1.2配置客户端详细信息

ClientDetailsServiceConfigurer可以将客户端详细信息配置在数据库,也可以配置在内存中(clientDetailsService),

clientDetailsService:clientDetailsService负责查找clientDetails

clientDetails:clientDetails中包括以下几个属性。

​ clientId:(必须的)用来标识客户的Id。

​ secret:(需要值得信任的客户端)客户端安全码,如果有的话。

​ scope:用来限制客户端的访问范围,如果为空(默认)的话,那么客户端拥有全部的访问范围。

​ authorizedGrantTypes:此客户端可以使用的授权类型,默认为空。

​ authorities:此客户端可以使用的权限

使用内存方式存储userDetails:

    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
            throws Exception {
    
        //clients.withClientDetails(clientDetailsService); //存储在数据库中
        clients.inMemory()// 存储在内存中
                .withClient("c1")// client_id,客户端id,
                .secret(new BCryptPasswordEncoder().encode("secret"))//客户端密钥
                .resourceIds("res1","res2")//资源列表这里是一个列表可以配置多个
                .authorizedGrantTypes("authorization_code", "password","client_credentials","implicit","refresh_token")// 该client允许的授权类型authorization_code,password,refresh_token,implicit,client_credentials
                .scopes("all")// 允许的授权范围
                .autoApprove(false)//false跳转到授权页面
                //加上验证回调地址
                .redirectUris("http://www.baidu.com"); //并且返回授权码
    }

客户端的信息可以采用存储在数据库中的形式:
在这里插入图片描述

    @Autowired
    PasswordEncoder passwordEncoder;

    //将客户端信息存储到数据库
    @Bean
    public ClientDetailsService clientDetailsService(DataSource dataSource) {
    
        ClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        ((JdbcClientDetailsService) clientDetailsService).setPasswordEncoder(passwordEncoder);
        return clientDetailsService;
    }
    public void configure(ClientDetailsServiceConfigurer clients)
            throws Exception {
    
        clients.withClientDetails(clientDetailsService);}
3.1.3配置令牌管理与访问端点

​ (1).配置令牌管理

​ 配置令牌存储单独写一个配置类进行配置,注入到spring容器中。

@Configuration
public class TokenConfig {
    

    //采用对称加密的方式生成令牌,统一在网关层进行校验
    private String SIGNING_KEY = "uaa123";

    @Bean
    public TokenStore tokenStore() {
    
        //JWT令牌存储方案
        return new JwtTokenStore(accessTokenConverter());
    }

    //配置令牌生成器
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
    
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SIGNING_KEY); //对称秘钥,资源服务器使用该秘钥来验证
        return converter;
    }
}

(2).定义AuthorizationServerTokenServices

​ 该接口实现了一些对令牌的管理。可以用它来修改令牌的格式与存储方式。

	//引入配置的授权令牌存储策略
	@Autowired
    private TokenStore tokenStore;

    @Autowired
    private ClientDetailsService clientDetailsService;
//令牌管理服务
    @Bean
    public AuthorizationServerTokenServices tokenService() {
    
        DefaultTokenServices service=new DefaultTokenServices();
        service.setClientDetailsService(clientDetailsService);//客户端详情服务
        service.setSupportRefreshToken(true);//支持刷新令牌
        service.setTokenStore(tokenStore);//令牌存储策略
        service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时
        service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天
        return service;
    }

配置授权码服务:AuthorizationServerEndpointsConfigurer 这个对象的实例可以完成令牌服务以及令牌endpoint配置。可以通过设置如下属性来决定支持的授权类型。

authenticationManager:认证管理器,当你选择了资源所有者密码(password)授权类型的时候,请设置这个属性注入一个 AuthenticationManager 对象。

authorizationCodeServices:这个属性是用来设置授权码服务的(即 AuthorizationCodeServices 的实例对象),主要用于 “authorization_code” 授权码类型模式。

@Bean
public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
    
    return new JdbcAuthorizationCodeServices(dataSource);//设置授权码模式的授权码如何存取
}

这里我们使用授权码模式,必须设置authorizationCodeServices

	@Autowired
    private AuthorizationCodeServices authorizationCodeServices; 
    @Autowired
    private AuthenticationManager authenticationManager;
@Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
    
        endpoints
                .authenticationManager(authenticationManager)//认证管理器
                .authorizationCodeServices(authorizationCodeServices)//授权码服务
                .tokenServices(tokenService())//令牌管理服务
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    }
3.1.4配置令牌访问约束

AuthorizationServerSecurityConfigurer:用来配置令牌端点的安全约束

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security){
    
                security
                 //oauth/token_key是公开
                .tokenKeyAccess("permitAll()")                    
                 //oauth/check_token公开,开启token检查:资源服务拿到令牌可以请求oauth/check_token进行验证,如果采用jwt令牌的方式这里可以不用开启
                .checkTokenAccess("permitAll()") 
                    //表单认证(申请令牌)
                .allowFormAuthenticationForClients();				
    }

1.tokenkey这个endpoint当使用JwtToken且使用非对称加密时,资源服务用于获取公钥而开放的,这里指这个
endpoint完全公开。
2.checkToken这个endpoint完全公开
3.允许表单认证

3.2资源服务配置

新建一个服务
在这里插入图片描述

创建资源服务配置类

@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResouceServerConfig extends
ResourceServerConfigurerAdapter {
    
    //资源的标识,这里与授权服务中clientDetailsService中配置的resourceIds("res1","res2")相对应
	public static final String RESOURCE_ID = "res1";
    
    //这里配置资源服务
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
    
    resources.resourceId(RESOURCE_ID)
    .tokenServices(tokenService())
    .stateless(true);
}
   //这里的配置类似于HttpSecurity访问权限等一系列配置
@Override
public void configure(HttpSecurity http) throws Exception {
    
    http
    .authorizeRequests()
        //这里配置授权服务中允许访问的范围。
    .antMatchers("/**").access("#oauth2.hasScope('all')")
    .and().csrf().disable()
    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}

4.统一认证授权

在这里插入图片描述
流程:
接入方经过网关请求授权服务授权,授权服务验证,并返回token给接入应用
接入方携带token访问资源。
在网关层,会校验该接入方的访问权限,并且解析token为明文,将该token下发给对应资源服务
在资源服务拿到token后,还会继续根据token中的身份信息来校验是否有权限访问对应资源。

4.1搭建注册中心

所有微服务的请求都经过网关,网关从注册中心读取微服务的地址,将请求转发至微服务。
在这里插入图片描述

配置application.yml

spring:
  application:
    name: security_discovery
server:
  port: 53000 #启动端口
eureka:
  server:
    enable-self-preservation: false    #关闭服务器自我保护,客户端心跳检测15分钟内错误达到80%服务会保护,导致别人还认为是好用的服务
  client:
    register-with-eureka: false  #false:不作为一个客户端注册到注册中心
    instance-info-replication-interval-seconds: 10
    serviceUrl:
      defaultZone: http://localhost:${
    server.port}/eureka/

4.2搭建网关

​ 认证服务器生成jwt令牌,所有请求都会通过网关层校验。在网关层拦截请求获取令牌解析,并转发给对应的服务。这样各资源服务就不需要关注令牌解析了。网关就从当了上文中资源服务器的角色。

在这里插入图片描述

网关采用zuul,统一认证服务与资源服务都是网关下微服务,需要在网关上新增路由配置

zuul.routes.uaa‐service.stripPrefix = false
zuul.routes.uaa‐service.path = /uaa/**
zuul.routes.user‐service.stripPrefix = false
zuul.routes.user‐service.path = /source/**
4.2.1网关转发token

1.从请求中获取令牌内容。

2.组装明文token,转发给微服务,放入请求头中

public class AuthFilter extends ZuulFilter {
    

    @Override
    public String filterType() {
    
        return "pre";
    }

    @Override
    public int filterOrder() {
    
        return 0;
    }

    @Override
    public boolean shouldFilter() {
    
        return true;
    }

    @Override
    public Object run() throws ZuulException {
    
        RequestContext currentContext = RequestContext.getCurrentContext();
        //从Security上下文中拿到用户身份对象
        SecurityContext authentication = SecurityContextHolder.getContext();
        //判断其是否是oauth授予的身份信息
        if(!(authentication instanceof OAuth2Authentication)){
    
            return null;
        }

        OAuth2Authentication oAuth2Authentication =(OAuth2Authentication) authentication;

        Authentication userAuthentication = oAuth2Authentication.getUserAuthentication();

//        取出用户身份信息
        String principal = userAuthentication.getName();

//        取出用户权限
        List<String> authorities =  new ArrayList<>();

        Collection<? extends GrantedAuthority> authorities1 = userAuthentication.getAuthorities();

        for(GrantedAuthority authentication1:authorities1 ) {
    
            authorities.add(authentication1.getAuthority());
        }

        OAuth2Request oAuth2Request = oAuth2Authentication.getOAuth2Request();
        Map<String, String> requestParameters = oAuth2Request.getRequestParameters();
        Map<String,Object> jsonToken = new HashMap<>(requestParameters);

        if(userAuthentication != null) {
    
            jsonToken.put("principal" ,principal);
            jsonToken.put("authorities",authorities);
        }
//        把用户身份信息和授权信息放在json中,加入http的header中,转发给微服务
        //对其进行base64编码
        currentContext.addZuulRequestHeader("json-token",EncryptUtil.encodeUTF8StringBase64(JSON.toJSONString(jsonToken)));
        return null;
    }
}

配置令牌配置类:

在授权服务中我配置了采用对称加密方式生成令牌的配置类,这里直接拷贝到网关服务。

public class TokenConfig {
    

    private String SIGNING_KEY = "uaa123";

    @Bean
    public TokenStore tokenStore() {
    
        //JWT令牌存储方案
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
    
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SIGNING_KEY); //对称秘钥,资源服务器使用该秘钥来验证
        return converter;
    }
4.2.2配置资源服务

主要配置的内容就是定义一些匹配规则,描述某个接入客户端需要什么样的权限才能访问某个微服务

@Configuration
public class ResouceServerConfig  {
    

    //资源的标识,这里与授权服务中clientDetailsService中配置的resourceIds("res1","res2")相对应
    public static final String RESOURCE_ID = "res1";

    //这里是对授权服务请求进行处理
    @Configuration
    @EnableResourceServer
    public class UAAServerConfig extends ResourceServerConfigurerAdapter {
    
        @Autowired
        private TokenStore tokenStore;

        @Override
        public void configure(ResourceServerSecurityConfigurer resources){
    
            resources.tokenStore(tokenStore).resourceId(RESOURCE_ID)
                    .stateless(true);
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
    
            //这里配置只要是授权服务的请求都不进行拦截
            http.authorizeRequests()
                 .antMatchers("/uaa/**").permitAll();
        }
    }

    //这里是对3.2中的资源服务的请求进行处理
    @Configuration
    @EnableResourceServer
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
        @Autowired
        private TokenStore tokenStore;

        @Override
        public void configure(ResourceServerSecurityConfigurer resources){
    
            //这里对令牌进行解析
            resources.tokenStore(tokenStore).resourceId(RESOURCE_ID)
                    .stateless(true);
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
    
            http
                    .authorizeRequests()
                	//这里配置授权服务中允许访问的范围。
                    .antMatchers("/resource/**").access("#oauth2.hasScope('ROLE_API')");
        }
    }
    //配置其它的资源服务..
}
4.4.3用户权限拦截

在下游微服务拿到token后,应该怎么实现权限拦截?自己解析明文token,自己实现一套访问策略?在这里还是可以使用spring security来认证token,在资源服务中新建filter,用于从请求中获取token并解析,拿到用户身份信息与权限,将其放入springsecurity上下文中。

@Component
public class TokenAuthenticationFilter extends OncePerRequestFilter {
    


    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
    
            //解析出头中的token
        String token = httpServletRequest.getHeader("json-token");
        if(token!=null){
    
            String json = EncryptUtil.decodeUTF8StringBase64(token);
            //将token转成json对象
            JSONObject jsonObject = JSON.parseObject(json);
            //用户身份信息
            UserDTO userDTO = JSON.parseObject(jsonObject.getString("principal"), UserDTO.class);
            //用户权限
            JSONArray authoritiesArray = jsonObject.getJSONArray("authorities");
            String[] authorities = authoritiesArray.toArray(new String[authoritiesArray.size()]);
            //将用户信息和权限填充 到用户身份token对象中
            UsernamePasswordAuthenticationToken authenticationToken
                    = new UsernamePasswordAuthenticationToken(userDTO,null, AuthorityUtils.createAuthorityList(authorities));
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
            //将authenticationToken填充到安全上下文
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            
        }
        filterChain.doFilter(httpServletRequest,httpServletResponse);

    }
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_39271545/article/details/118197556

智能推荐

稀疏编码的数学基础与理论分析-程序员宅基地

文章浏览阅读290次,点赞8次,收藏10次。1.背景介绍稀疏编码是一种用于处理稀疏数据的编码技术,其主要应用于信息传输、存储和处理等领域。稀疏数据是指数据中大部分元素为零或近似于零的数据,例如文本、图像、音频、视频等。稀疏编码的核心思想是将稀疏数据表示为非零元素和它们对应的位置信息,从而减少存储空间和计算复杂度。稀疏编码的研究起源于1990年代,随着大数据时代的到来,稀疏编码技术的应用范围和影响力不断扩大。目前,稀疏编码已经成为计算...

EasyGBS国标流媒体服务器GB28181国标方案安装使用文档-程序员宅基地

文章浏览阅读217次。EasyGBS - GB28181 国标方案安装使用文档下载安装包下载,正式使用需商业授权, 功能一致在线演示在线API架构图EasySIPCMSSIP 中心信令服务, 单节点, 自带一个 Redis Server, 随 EasySIPCMS 自启动, 不需要手动运行EasySIPSMSSIP 流媒体服务, 根..._easygbs-windows-2.6.0-23042316使用文档

【Web】记录巅峰极客2023 BabyURL题目复现——Jackson原生链_原生jackson 反序列化链子-程序员宅基地

文章浏览阅读1.2k次,点赞27次,收藏7次。2023巅峰极客 BabyURL之前AliyunCTF Bypassit I这题考查了这样一条链子:其实就是Jackson的原生反序列化利用今天复现的这题也是大同小异,一起来整一下。_原生jackson 反序列化链子

一文搞懂SpringCloud,详解干货,做好笔记_spring cloud-程序员宅基地

文章浏览阅读734次,点赞9次,收藏7次。微服务架构简单的说就是将单体应用进一步拆分,拆分成更小的服务,每个服务都是一个可以独立运行的项目。这么多小服务,如何管理他们?(服务治理 注册中心[服务注册 发现 剔除])这么多小服务,他们之间如何通讯?这么多小服务,客户端怎么访问他们?(网关)这么多小服务,一旦出现问题了,应该如何自处理?(容错)这么多小服务,一旦出现问题了,应该如何排错?(链路追踪)对于上面的问题,是任何一个微服务设计者都不能绕过去的,因此大部分的微服务产品都针对每一个问题提供了相应的组件来解决它们。_spring cloud

Js实现图片点击切换与轮播-程序员宅基地

文章浏览阅读5.9k次,点赞6次,收藏20次。Js实现图片点击切换与轮播图片点击切换<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title></title> <script type="text/ja..._点击图片进行轮播图切换

tensorflow-gpu版本安装教程(过程详细)_tensorflow gpu版本安装-程序员宅基地

文章浏览阅读10w+次,点赞245次,收藏1.5k次。在开始安装前,如果你的电脑装过tensorflow,请先把他们卸载干净,包括依赖的包(tensorflow-estimator、tensorboard、tensorflow、keras-applications、keras-preprocessing),不然后续安装了tensorflow-gpu可能会出现找不到cuda的问题。cuda、cudnn。..._tensorflow gpu版本安装

随便推点

物联网时代 权限滥用漏洞的攻击及防御-程序员宅基地

文章浏览阅读243次。0x00 简介权限滥用漏洞一般归类于逻辑问题,是指服务端功能开放过多或权限限制不严格,导致攻击者可以通过直接或间接调用的方式达到攻击效果。随着物联网时代的到来,这种漏洞已经屡见不鲜,各种漏洞组合利用也是千奇百怪、五花八门,这里总结漏洞是为了更好地应对和预防,如有不妥之处还请业内人士多多指教。0x01 背景2014年4月,在比特币飞涨的时代某网站曾经..._使用物联网漏洞的使用者

Visual Odometry and Depth Calculation--Epipolar Geometry--Direct Method--PnP_normalized plane coordinates-程序员宅基地

文章浏览阅读786次。A. Epipolar geometry and triangulationThe epipolar geometry mainly adopts the feature point method, such as SIFT, SURF and ORB, etc. to obtain the feature points corresponding to two frames of images. As shown in Figure 1, let the first image be ​ and th_normalized plane coordinates

开放信息抽取(OIE)系统(三)-- 第二代开放信息抽取系统(人工规则, rule-based, 先抽取关系)_语义角色增强的关系抽取-程序员宅基地

文章浏览阅读708次,点赞2次,收藏3次。开放信息抽取(OIE)系统(三)-- 第二代开放信息抽取系统(人工规则, rule-based, 先关系再实体)一.第二代开放信息抽取系统背景​ 第一代开放信息抽取系统(Open Information Extraction, OIE, learning-based, 自学习, 先抽取实体)通常抽取大量冗余信息,为了消除这些冗余信息,诞生了第二代开放信息抽取系统。二.第二代开放信息抽取系统历史第二代开放信息抽取系统着眼于解决第一代系统的三大问题: 大量非信息性提取(即省略关键信息的提取)、_语义角色增强的关系抽取

10个顶尖响应式HTML5网页_html欢迎页面-程序员宅基地

文章浏览阅读1.1w次,点赞6次,收藏51次。快速完成网页设计,10个顶尖响应式HTML5网页模板助你一臂之力为了寻找一个优质的网页模板,网页设计师和开发者往往可能会花上大半天的时间。不过幸运的是,现在的网页设计师和开发人员已经开始共享HTML5,Bootstrap和CSS3中的免费网页模板资源。鉴于网站模板的灵活性和强大的功能,现在广大设计师和开发者对html5网站的实际需求日益增长。为了造福大众,Mockplus的小伙伴整理了2018年最..._html欢迎页面

计算机二级 考试科目,2018全国计算机等级考试调整,一、二级都增加了考试科目...-程序员宅基地

文章浏览阅读282次。原标题:2018全国计算机等级考试调整,一、二级都增加了考试科目全国计算机等级考试将于9月15-17日举行。在备考的最后冲刺阶段,小编为大家整理了今年新公布的全国计算机等级考试调整方案,希望对备考的小伙伴有所帮助,快随小编往下看吧!从2018年3月开始,全国计算机等级考试实施2018版考试大纲,并按新体系开考各个考试级别。具体调整内容如下:一、考试级别及科目1.一级新增“网络安全素质教育”科目(代..._计算机二级增报科目什么意思

conan简单使用_apt install conan-程序员宅基地

文章浏览阅读240次。conan简单使用。_apt install conan