迁移前的提交
|
@ -0,0 +1,119 @@
|
||||||
|
---
|
||||||
|
title: GeoServer安全-AuthKey
|
||||||
|
author: ac
|
||||||
|
date: 2013-12-17
|
||||||
|
categries:
|
||||||
|
- GIS
|
||||||
|
tags:
|
||||||
|
- AuthKey
|
||||||
|
---
|
||||||
|
|
||||||
|
## GeoServer安全-AuthKey
|
||||||
|
|
||||||
|
### 1.简述
|
||||||
|
|
||||||
|
`authkey` 鉴权可以在GeoServer中实现像调用天地图、高德地图那样,通过提供URL中的Key进行鉴权,如:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://localhost:8080/geoserver/topp/wms?service=WMS&version=1.3.0&request=GetCapabilities&authkey=ef18d7e7-963b-470f-9230-c7f9de166888
|
||||||
|
```
|
||||||
|
|
||||||
|
其中的`authkey`与特定用户相关联,作为唯一身份验证令牌。
|
||||||
|
|
||||||
|
### 2. 插件安装
|
||||||
|
|
||||||
|
下载[Key authentication](https://sourceforge.net/projects/geoserver/files/GeoServer/2.22.1/extensions/geoserver-2.22.1-authkey-plugin.zip) 模块
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
解压到 `webapps\geoserver\WEB-INF\lib` 目录下,重启应用。
|
||||||
|
|
||||||
|
### 3.插件配置
|
||||||
|
|
||||||
|
#### 3.1 添加认证`user property`
|
||||||
|
|
||||||
|
新增验证过滤器,这里有两种方式`user property`,`web service`;我们先选这第一种:
|
||||||
|
|
||||||
|
> 第一种:是在GeoServer 内部进行authkey的验证。
|
||||||
|
>
|
||||||
|
> 第二种是通过调用外部接口,在外部验证,正确后返回一个用户名的方式进行鉴权。
|
||||||
|
|
||||||
|
<img src="./images/image-20231218105240848.png" alt="image-20231218105240848" style="zoom:50%;display:block;width:50%;" /><img src="images/image-20231218110226274.png" alt="image-20231218110226274" style="zoom:80%;display:block;width:50%" />
|
||||||
|
|
||||||
|
点击`Synchronize user/group service` 将会生成对应用户/组服务中用户的UUID的key。
|
||||||
|
|
||||||
|
<img src="images/image-20231218110655977.png" alt="image-20231218110655977" style="zoom:80%;" />
|
||||||
|
|
||||||
|
#### 3.2 添加过滤器链
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<img src="images/image-20231218111238452.png" alt="image-20231218111238452" style="zoom:80%;" />
|
||||||
|
|
||||||
|
将新增的`prop_auth`认证添加到选择的框中,去除匿名的认证`anonymous`,关闭,保存。
|
||||||
|
|
||||||
|
#### 3.3 测试
|
||||||
|
|
||||||
|
`http://localhost:8080/geoserver/nurc/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&FORMAT=image%2Fjpeg&TRANSPARENT=true&STYLES&LAYERS=nurc%3AArc_Sample&exceptions=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A4326&WIDTH=768&HEIGHT=384&BBOX=-293.12500078125%2C-127.18750078125001%2C246.71875078124998%2C142.65625078125`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
返回的是401,`HTTP ERROR 401 Unauthorized`。添加`authKey`参数后才可以正常访问:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 3.4 添加认证`web service`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
在后台实现一个接口,接受authkey参数。
|
||||||
|
|
||||||
|
```java
|
||||||
|
@GetMapping(value="/checkAuthKey")
|
||||||
|
public String checkAuthKey(@RequestParam String authkey){
|
||||||
|
try{
|
||||||
|
String username = sysUserService.checkAuthKey(authkey);
|
||||||
|
if(StringUtils.isBlank(username)){
|
||||||
|
System.out.println("没有关联的username");
|
||||||
|
}
|
||||||
|
return username;
|
||||||
|
}catch (Exception e){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return "pg_admin";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.5 添加过滤器链
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
只保留刚刚新建的service_auth认证。
|
||||||
|
|
||||||
|
#### 3.6 验证
|
||||||
|
|
||||||
|
`http://localhost:8080/geoserver/tiger/wms?service=WMS&version=1.1.0&request=GetMap&layers=tiger:poi&bbox=-74.0118315772888,40.70754683896324,-74.00153046439813,40.719885123828675&width=641&height=768&srs=EPSG:4326&styles=&format=image/jpeg`
|
||||||
|
|
||||||
|
没有添加`authKey`参数,后台响应式`HTTP ERROR 403 Access Denied`,没有权限。
|
||||||
|
|
||||||
|
添加后可以正常访问服务:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 4.总结
|
||||||
|
|
||||||
|
AuthKey插件可以让访问GeoServer的请求都添加一步认证的过程,只有认证通过的请求才能正常返回。
|
||||||
|
|
||||||
|
这种方式可以理解成在GeoServer服务端与客户端之间加了一堵墙而已,而进一步的细化的服务安全(服务操作)和图层数据安全(数据访问规则)需要在服务端另外进行配置。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 参考文章
|
||||||
|
|
||||||
|
[1] [密钥验证模块 — GeoServer 2.24.x User Manual (osgeo.cn)](https://www.osgeo.cn/geoserver-user-manual/extensions/authkey/index.html)
|
||||||
|
|
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 94 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 89 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 139 KiB |
After Width: | Height: | Size: 239 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 75 KiB |
|
@ -0,0 +1,652 @@
|
||||||
|
## GeoServer Security
|
||||||
|
|
||||||
|
> GeoServer中的Security子系统是基于Spring Security的。这里介绍的是通过GeoServer的Web管理界面来如何配置Security。
|
||||||
|
|
||||||
|
### 1. Security 设置
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 密码
|
||||||
|
|
||||||
|
> 密码是任何安全系统的核心部分。
|
||||||
|
|
||||||
|
`GeoServer`配置存储两种类型的密码:
|
||||||
|
|
||||||
|
- 访问`GeoServer`资源的用户帐号密码;
|
||||||
|
- 内部用于访问外部服务(如数据库`postgis`和关联`OGC`服务)的密码。
|
||||||
|
|
||||||
|
由于这些密码通常存储在磁盘上,因此强烈建议对它们进行加密,而不是存储为可读的明文文本。`GeoServer`安全提供了四种密码加密方案:
|
||||||
|
|
||||||
|
1. **空密码**:该方案是不可逆转的。任何密码都被编码为空字符串,因此无法重新计算纯文本密码。该方案用于用户/组服务,并结合使用后端系统的身份验证机制。例如,针对`LDAP`服务器或`JDBC`数据库的用户名/密码身份验证。在这些场景中,将密码本地存储到`GeoServer`是没有意义的。
|
||||||
|
2. **明文密码**:纯文本密码根本不提供加密。在这种情况下,任何可以访问文件系统的人都可以读取密码。由于显而易见的原因,除了最基本的测试服务器之外,不建议使用这种方法。
|
||||||
|
3. **摘要密码(可加盐)**:摘要加密是不可逆转的。它通过迭代过程对密码应用`SHA-256`加密散列函数10万次。该方案是“单向的”,因为实际上不可能从其散列表示中反转和获取原始密码。为了防止已知的攻击,在生成密钥时将一个称为salt的随机值添加到密码中。对于每次消化,使用一种单独的随机盐。对相同密码进行两次摘要处理会得到不同的散列表示。
|
||||||
|
4. **`PBE`密码**:[Password-based encryption](http://www.javamex.com/tutorials/cryptography/password_based_encryption.shtml)基于密码的加密(`PBE`)通常使用用户提供的密码来生成加密密钥。这个方案是可逆的。
|
||||||
|
|
||||||
|
密码加密方案是全局设置的,会影响外部资源密码加密的,同时也指定为每个`user/group`服务的加密方案。
|
||||||
|
|
||||||
|
外部资源的加密方案必须是可逆的,而用户/组服务可以使用任何方案。
|
||||||
|
|
||||||
|
#### Password-based encryption
|
||||||
|
|
||||||
|
`GeoServer`支持两种`PBE`:
|
||||||
|
|
||||||
|
- `Weak PBE`:弱`PBE` (`GeoServer`默认值)使用一种相对容易破解的基本加密方法。加密密钥使用`MD5`迭代1000次从密码中获得。加密算法本身是DES (Data encryption Standard)。DES的有效密钥长度为56位,这对于当今的计算机系统来说并不是一个真正的挑战。
|
||||||
|
- `Strong PBE`:`强PBE`使用更强大的加密方法,基于`AES 256`位算法和`CBC`。密钥长度为256位,使用`SHA-256`而不是`MD5`导出。强烈建议使用`Strong PBE`。
|
||||||
|
|
||||||
|
以“`geoserver`”密码加密为“`crypt1:KWhO7jrTz/Gi0oTQRKsVeCmWIZY5VZaD`”为例:
|
||||||
|
|
||||||
|
> 密文和盐是64进制编码的。
|
||||||
|
|
||||||
|
- `crypt1`表示`弱PBE`的使用情况。
|
||||||
|
- `强PBE`的前缀是`crypt2`。
|
||||||
|
|
||||||
|
#### 可逆加密
|
||||||
|
|
||||||
|
密码加密方法可以是可逆的,这意味着有可能(也是可取的)从其加密版本获得明文密码。可逆密码对于数据库连接或外部`OGC`服务(如级联`WMS`和级联`WFS`)是必需的,因为`GeoServer`必须能够解码加密的密码并将其传递给外部服务。明文密码和`PBE`密码是可逆的。
|
||||||
|
|
||||||
|
不可逆转的密码提供了最高级别的安全性,因此应该用于用户帐户和其他任何可能的地方。强烈建议使用密码摘要,不需要安装不受限制的策略文件。
|
||||||
|
|
||||||
|
#### 密钥和密钥库
|
||||||
|
|
||||||
|
要使可逆密码提供有意义的安全级别,必须以某种方式限制对密码的访问。
|
||||||
|
|
||||||
|
在`GeoServer`中,密码的加密和解密涉及到秘密共享和密钥生成,这些密钥存储在典型的Java密钥库中。`GeoServer`使用自己的密钥存储库,名为`GeoServer.jceks`,它位于`GeoServer`数据目录下的security目录中。该文件以`JCEKS`格式存储,而不是默认的`JKS`格式。`JKS`不支持存储共享密钥。
|
||||||
|
|
||||||
|
还可以为`GeoServer`设置密钥存储库密码。这个密码有两个用途:
|
||||||
|
|
||||||
|
- 保护对密钥存储库的访问
|
||||||
|
- 保护对`GeoServer Root`帐户的访问权限
|
||||||
|
|
||||||
|
默认情况下,使用纯文本生成`keystore`密码并将其存储在名为`security/masterpw.info`的文件中。
|
||||||
|
|
||||||
|
从现有的`GeoServer`数据目录(2.1版本及以下),则该算法尝试找出角色为`ROLE_ADMINISTRATOR`的用户的密码。如果发现该密码,且密码长度至少为8个字符,则`GeoServer`使用该密码作为`keystore`密码。同样,所选用户的名称可以在`security/masterpw.info`中找到。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> `GeoServer`,读取该文件并验证密钥存储库密码。成功后,该文件将被删除。
|
||||||
|
>
|
||||||
|
> 如果删除security文件夹,用户的密码和`postgis`的密码都会被删除,所以谨慎处理。
|
||||||
|
|
||||||
|
设置一个Active keystore password provider:
|
||||||
|
|
||||||
|
1. 
|
||||||
|
2. 
|
||||||
|
3. Changing the keystore password,密码策略要求最少要8个字符
|
||||||
|
4. 
|
||||||
|
5. 更改完成后,摘要密码`masterpw.digest`会更新,同时`security/masterpw/default/passwd`也会更新。
|
||||||
|
|
||||||
|
默认情况下,禁用使用“`Keystore Password`”登录`Admin GUI`和`REST api`。为了启用它,您需要手动更改密钥库密码提供程序`config.xml`,通常位于`security/masterpw/default/config.xml`中,通过添加以下语句:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<loginEnabled>true</loginEnabled>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 密码策略
|
||||||
|
|
||||||
|
密码策略定义了对密码的约束,如密码长度、大小写和所需的字符类组合。密码策略在添加用户/组业务时指定,用于创建新用户和修改现有用户密码时对密码进行约束。
|
||||||
|
|
||||||
|
#### 参数化的密码
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 角色系统
|
||||||
|
|
||||||
|
GeoServer中的安全性是基于**角色**的系统,其中创建了用于服务特定功能的角色。具有特定功能的角色的示例包括访问Web Feature Service (WFS)、管理Web管理接口和读取特定层的角色。
|
||||||
|
|
||||||
|
角色分配给用户和用户组,并确定允许这些用户或组执行哪些操作。
|
||||||
|
|
||||||
|
通过认证方式对用户进行授权。
|
||||||
|
|
||||||
|
#### 用户与组
|
||||||
|
|
||||||
|
GeoServer用户的定义与大多数安全系统类似。虽然正确的Java术语是**principal**主体(主体是指人、计算机、软件系统等),但在整个GeoServer文档中都采用了用户一词。为每个用户维护以下信息:
|
||||||
|
|
||||||
|
- 用户名
|
||||||
|
- 密码
|
||||||
|
- 指示用户是否启用的标志(这是默认值)。禁用的用户被禁止登录。现有用户会话不受影响。
|
||||||
|
- 键/值对的集合
|
||||||
|
|
||||||
|
键/值对是特定于实现的,可以由用户或组所属的用户/组服务配置。例如,维护用户信息(如姓名、电子邮件地址等)的用户/组服务可能希望将这些属性与用户对象关联起来
|
||||||
|
|
||||||
|
一个GeoServer组 **group** 就是一组用户。对于每一组,保持以下信息:
|
||||||
|
|
||||||
|
- Group name
|
||||||
|
- 指示组是否启用的标志(这是默认值)。禁用的组不参与该组中包含的所有用户的角色计算。
|
||||||
|
- 属于该组的用户列表
|
||||||
|
|
||||||
|
#### 用户/组服务
|
||||||
|
|
||||||
|
用户/组服务为用户/组提供以下信息:
|
||||||
|
|
||||||
|
- 用户列表
|
||||||
|
- 组列表,包括与每个组关联的用户
|
||||||
|
- 用户密码
|
||||||
|
|
||||||
|
许多身份验证提供者将使用用户/组服务来执行身份验证。在这种情况下,用户/组服务将是对用户和密码进行身份验证的数据库。根据[Authentication chain](https://docs.geoserver.org/latest/en/user/security/auth/chain.html#security-auth-chain) 身份验证链的配置方式,在任何给定时间可能有零个、一个或多个活跃的用户/组服务。
|
||||||
|
|
||||||
|
用户/组服务可能是只读的,提供对用户信息的访问,但不允许添加或修改新的用户和组。如果将用户/组服务配置为将用户和组数据库委托给外部服务,则可能发生这种情况。外部LDAP服务器就是一个例子。
|
||||||
|
|
||||||
|
默认情况下,GeoServer支持三种类型的用户/组服务:
|
||||||
|
|
||||||
|
- [XML](https://docs.geoserver.org/latest/en/user/security/usergrouprole/usergroupservices.html#security-rolesystem-usergroupxml)—*(Default)* User/group service persisted as XML
|
||||||
|
- User/group service persisted in database via JDBC
|
||||||
|
- [LDAP](https://docs.geoserver.org/latest/en/user/security/usergrouprole/usergroupservices.html#security-rolesystem-usergroupldap)—User/group service obtained from an LDAP repository
|
||||||
|
|
||||||
|
还可以向GeoServer添加其他服务,例如 [AuthKey](https://docs.geoserver.org/latest/en/user/extensions/authkey/index.html#authkey) 扩展提供的服务。
|
||||||
|
|
||||||
|
**XML user/group service**
|
||||||
|
|
||||||
|
XML用户/组服务将用户/组数据库保存在XML文件中。这是GeoServer的默认行为。
|
||||||
|
|
||||||
|
该服务将用户数据库表示为XML,并与此XML schema模式相对应。
|
||||||
|
|
||||||
|
> XML用户/组文件users.xml位于GeoServer数据目录security/usergroup/<name>/users.xml中,其中<name>是用户/组服务的名称。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
以下是默认GeoServer配置附带的users.xml的内容:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<userRegistry version="1.0" xmlns="http://www.geoserver.org/security/users">
|
||||||
|
<users>
|
||||||
|
<user enabled="true" name="admin" password="digest1:D9miJH/hVgfxZJscMafEtbtliG0ROxhLfsznyWfG38X2pda2JOSV4POi55PQI4tw"/>
|
||||||
|
</users>
|
||||||
|
<groups/>
|
||||||
|
</userRegistry>
|
||||||
|
```
|
||||||
|
|
||||||
|
**JDBC user/group service**
|
||||||
|
|
||||||
|
JDBC user/group service 通过JDBC调用数据库,持久化用户/组数据,管理多个表中的用户信息。用户/组数据库模式如下:
|
||||||
|
|
||||||
|
用户表:`users`
|
||||||
|
|
||||||
|
| Field | Type | Null | Key |
|
||||||
|
| :------- | :----------- | :--- | :--- |
|
||||||
|
| name | varchar(128) | NO | PRI |
|
||||||
|
| password | varchar(254) | YES | |
|
||||||
|
| enabled | char(1) | NO | |
|
||||||
|
|
||||||
|
用户属性表:`user_props`
|
||||||
|
|
||||||
|
| Field | Type | Null | Key |
|
||||||
|
| :-------- | :------------ | :--- | :--- |
|
||||||
|
| username | varchar(128) | NO | PRI |
|
||||||
|
| propname | varchar(64) | NO | PRI |
|
||||||
|
| propvalue | varchar(2048) | YES | |
|
||||||
|
|
||||||
|
用户组信息表:`groups`
|
||||||
|
|
||||||
|
| Field | Type | Null | Key |
|
||||||
|
| :------ | :----------- | :--- | :--- |
|
||||||
|
| name | varchar(128) | NO | PRI |
|
||||||
|
| enabled | char(1) | NO | |
|
||||||
|
|
||||||
|
用户组成员表:`group_members`
|
||||||
|
|
||||||
|
| Field | Type | Null | Key |
|
||||||
|
| :-------- | :----------- | :--- | :--- |
|
||||||
|
| groupname | varchar(128) | NO | PRI |
|
||||||
|
| username | varchar(128) | NO | PRI |
|
||||||
|
|
||||||
|
`users`表是主表,包含带有关联密码的用户列表;
|
||||||
|
|
||||||
|
`user_props`表将附加属性映射到用户;
|
||||||
|
|
||||||
|
`groups`表列出了所有可用的组;
|
||||||
|
|
||||||
|
`group_members`表映射了哪些用户属于哪些组。
|
||||||
|
|
||||||
|
> 默认的GeoServer安全配置中上述四张表都是空的
|
||||||
|
|
||||||
|
**LDAP user/group service**
|
||||||
|
|
||||||
|
LDAP用户/组服务是一个**只读**用户/组服务,它将用户和组从LDAP存储库映射到GeoServer用户和组。
|
||||||
|
|
||||||
|
用户从特定的LDAP节点中提取,配置为Users搜索库。组是从特定的LDAP节点中提取的,并配置为Groups搜索基。每个匹配的用户对应一个用户映射,每个匹配的组对应一个组映射。
|
||||||
|
|
||||||
|
> LDAP(Light Directory Access Portocol),它是基于X.500标准的轻量级目录访问协议。目录是一个为查询、浏览和搜索而优化的数据库,它成树状结构组织数据,类似文件目录一样。
|
||||||
|
>
|
||||||
|
> LDAP目录服务是由目录数据库和一套访问协议组成的系统。
|
||||||
|
|
||||||
|
可以指定包含组名(如cn)、用户名(如uid)以及两者之间的成员关系(如member)的属性。但是,也可以指定特定的过滤器来搜索所有用户/组(例如cn=*),按名称查找用户/组(例如cn={0})并将用户映射到组(例如member={0})。这些过滤器也可以从属性名中自动派生出来。另外,如果提供了过滤器,属性名也可以保留为空。
|
||||||
|
|
||||||
|
对于用户,可以通过提供一个逗号分隔的属性名列表,从LDAP服务器填充其他属性(键/值对,请参阅users和Groups)。
|
||||||
|
|
||||||
|
检索用户/组信息可以匿名完成,如果LDAP存储库需要,也可以使用给定的用户名/密码。
|
||||||
|
|
||||||
|
此类型角色服务的配置文件(config.xml)示例如下:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<userGroupService>
|
||||||
|
<id>52857278:13c7ffd66a8:-7ffd</id>
|
||||||
|
<name>default</name>
|
||||||
|
<className>org.geoserver.security.xml.XMLUserGroupService</className>
|
||||||
|
<fileName>users.xml</fileName>
|
||||||
|
<checkInterval>10000</checkInterval>
|
||||||
|
<validating>true</validating>
|
||||||
|
<passwordEncoderName>digestPasswordEncoder</passwordEncoderName>
|
||||||
|
<passwordPolicyName>default</passwordPolicyName>
|
||||||
|
</userGroupService>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 角色
|
||||||
|
|
||||||
|
GeoServer角色是与执行某些任务或访问特定资源相关联的关键。角色被分配给用户`users`和组`groups`,授权他们执行与角色相关的操作。一个GeoServer角色包括以下内容:
|
||||||
|
|
||||||
|
- 角色名
|
||||||
|
- 父角色
|
||||||
|
- 设置键值对
|
||||||
|
|
||||||
|
GeoServer角色支持继承—子角色继承授予父角色的所有访问权限。
|
||||||
|
|
||||||
|
键/值对是特定于实现的,可以由用户或组所属的角色服务配置。例如,基于员工组织分配角色的角色服务可能希望将其他信息(如Department Name)与角色关联。
|
||||||
|
|
||||||
|
GeoServer有许多系统角色,这些角色的名称是保留的。不允许添加具有保留名称的新GeoServer角色。
|
||||||
|
|
||||||
|
- `ROLE_ADMINISTRATOR`:提供对所有操作和资源的访问
|
||||||
|
- `ROLE_GROUP_ADMIN`:管理用户组的特殊角色
|
||||||
|
- `ROLE_AUTHENTICATED`:分配给每个身份验证成功的用户
|
||||||
|
- `ROLE_ANONYMOUS`:启用匿名身份验证且用户未登录时分配
|
||||||
|
|
||||||
|
#### 角色服务
|
||||||
|
|
||||||
|
角色服务为角色提供如下信息:
|
||||||
|
|
||||||
|
- 角色列表
|
||||||
|
- 计算给定用户的角色分配
|
||||||
|
- 角色到系统角色ROLE_ADMINISTRATOR的映射
|
||||||
|
- 角色到系统角色ROLE_GROUP_ADMIN的映射
|
||||||
|
|
||||||
|
当用户/组服务加载有关用户或组的信息时,它将委托给角色服务来确定应该将哪些角色分配给用户或组。
|
||||||
|
|
||||||
|
与用户/组服务不同,**在任何给定时间只有一个角色服务是活动的**。在“设置”页面中设置默认角色服务。
|
||||||
|
|
||||||
|
默认情况下,GeoServer支持两种类型的角色服务:
|
||||||
|
|
||||||
|
- XML——(默认)角色服务作为XML持久化
|
||||||
|
- JDBC——角色服务通过JDBC持久化在数据库中
|
||||||
|
|
||||||
|
**将角色映射到系统角色**
|
||||||
|
|
||||||
|
若要将系统角色`ROLE_ADMINISTRATOR`分配给用户或组,则需要创建不同名称的新角色,并将其映射给角色`ROLE_ADMINISTRATOR`。对于系统角色`ROLE_GROUP_ADMIN`也是如此。映射存储在服务的config.xml文件中。
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<roleService>
|
||||||
|
<id>52857278:13c7ffd66a8:-7ffc</id>
|
||||||
|
<name>default</name>
|
||||||
|
<className>org.geoserver.security.xml.XMLRoleService</className>
|
||||||
|
<fileName>roles.xml</fileName>
|
||||||
|
<checkInterval>10000</checkInterval>
|
||||||
|
<validating>true</validating>
|
||||||
|
<adminRoleName>ADMIN</adminRoleName>
|
||||||
|
<groupAdminRoleName>GROUP_ADMIN</groupAdminRoleName>
|
||||||
|
</roleService>
|
||||||
|
```
|
||||||
|
|
||||||
|
在本例中,赋予角色ADMIN的用户或组同时赋予系统角色ROLE_ADMINISTRATOR。对于GROUP_ADMIN和ROLE_GROUP_ADMIN也是如此。
|
||||||
|
|
||||||
|
**XML角色服务**
|
||||||
|
|
||||||
|
XML角色服务将角色数据库保存在XML文件中(这是GeoServer的默认角色服务)。该服务将用户数据库表示为XML,并与此XML模式相对应。
|
||||||
|
|
||||||
|
> XML角色文件roles.xml位于GeoServer数据目录security/role/<name>/roles.xml中,其中<name>是角色服务的名称。
|
||||||
|
|
||||||
|
配置本地角色“ADMIN”映射到系统角色“ROLE_ADMINISTRATOR”。另外,GROUP_ADMIN映射到ROLE_GROUP_ADMIN。映射存储在每个角色服务的config.xml文件中。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<roleRegistry version="1.0" xmlns="http://www.geoserver.org/security/roles">
|
||||||
|
<roleList>
|
||||||
|
<role id="ADMIN"/>
|
||||||
|
<role id="GROUP_ADMIN"/>
|
||||||
|
</roleList>
|
||||||
|
<userList>
|
||||||
|
<userRoles username="admin">
|
||||||
|
<roleRef roleID="ADMIN"/>
|
||||||
|
</userRoles>
|
||||||
|
</userList>
|
||||||
|
<groupList/>
|
||||||
|
</roleRegistry>
|
||||||
|
```
|
||||||
|
|
||||||
|
该配置包含ADMIN和GROUP_ADMIN两个角色。角色ADMIN被分配给ADMIN用户。由于ADMIN角色已经映射给系统角色ROLE_ADMINISTRATOR,所以在角色计算中,ADMIN用户被同时赋予了两个角色
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**JDBC角色服务**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
JDBC角色服务通过JDBC持久化角色数据库,管理多个表中的角色信息。角色数据库模式如下所示
|
||||||
|
|
||||||
|
角色表:roles
|
||||||
|
|
||||||
|
| Field | Type | Null | Key |
|
||||||
|
| :----- | :---------- | :--- | :--- |
|
||||||
|
| name | varchar(64) | NO | PRI |
|
||||||
|
| parent | varchar(64) | YES | |
|
||||||
|
|
||||||
|
角色属性表:role_props
|
||||||
|
|
||||||
|
| Field | Type | Null | Key |
|
||||||
|
| :-------- | :------------ | :--- | :--- |
|
||||||
|
| rolename | varchar(64) | NO | PRI |
|
||||||
|
| propname | varchar(64) | NO | PRI |
|
||||||
|
| propvalue | varchar(2048) | YES | |
|
||||||
|
|
||||||
|
用户角色关联表:user_roles
|
||||||
|
|
||||||
|
| Field | Type | Null | Key |
|
||||||
|
| :------- | :----------- | :--- | :--- |
|
||||||
|
| username | varchar(128) | NO | PRI |
|
||||||
|
| rolename | varchar(64) | NO | PRI |
|
||||||
|
|
||||||
|
用户组与角色关联表:group_roles
|
||||||
|
|
||||||
|
| Field | Type | Null | Key |
|
||||||
|
| :-------- | :----------- | :--- | :--- |
|
||||||
|
| groupname | varchar(128) | NO | PRI |
|
||||||
|
| rolename | varchar(64) | NO | PRI |
|
||||||
|
|
||||||
|
- roles表是主表,包含角色列表。GeoServer中的角色支持继承,所以一个角色可以有一个到父角色的链接。
|
||||||
|
- role_props表将其他属性映射到角色。
|
||||||
|
- user_roles表将用户映射到分配给他们的角色。
|
||||||
|
- group_roles表映射了哪些组被分配了哪些角色。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
GeoServer中上述四张表都为空。
|
||||||
|
|
||||||
|
添加角色:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
使用:在安全设置中选择刚刚新建的角色服务:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
在去`用户/组`中添加用户:
|
||||||
|
|
||||||
|
<img src="./images/image-20231215150154974.png" alt="image-20231215150154974" style="zoom: 50%;" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**REST role service**
|
||||||
|
|
||||||
|
REST角色服务是一个**只读**角色服务,它将组和关联用户映射到远程REST web服务中的角色.
|
||||||
|
|
||||||
|
REST服务必须支持JSON编码。
|
||||||
|
|
||||||
|
下面是REST角色服务(基于LDAP角色服务,它同样必须使网络调用工作)提供的重要方法的列表:
|
||||||
|
|
||||||
|
| Method | Mandatory |
|
||||||
|
| :------------------------------ | :----------------------------------------------------------- |
|
||||||
|
| *getUserNamesForRole(roleName)* | N (implemented in LDAP, but I don’t see actual users of this method besides a utility method that nobody uses) |
|
||||||
|
| *getRolesForUser(user)* | Y |
|
||||||
|
| *getRolesForGroup(group)* | N |
|
||||||
|
| *getRoles()* | Y (used by the UI) |
|
||||||
|
| *getParentRole(role)* | N |
|
||||||
|
| *getAdminRole()* | Y |
|
||||||
|
| *getGroupAdminRole()* | Y |
|
||||||
|
| *getRoleCount()* | Y (does not seem to be used much, we can trivially implement it from getRoles() |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 角色服务与用户/组
|
||||||
|
|
||||||
|
**计算用户角色**
|
||||||
|
|
||||||
|
下图说明了用户/组服务和角色服务如何交互以计算用户角色。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
在从用户/组服务中获取启用的用户时,必须标识分配给该用户的角色。鉴定程序为:
|
||||||
|
|
||||||
|
1. 获取该用户`user`的所有已启用组。如果一个组被禁用,它将被丢弃。
|
||||||
|
2. 获取与该用户关联的所有角色`role`,并将角色添加到结果集中。
|
||||||
|
3. 对于用户所属的每个已启用组,获取与该组关联的所有角色,并将角色添加到结果集中。
|
||||||
|
4. 对于结果集中的每个角色,获取所有祖先角色并将这些角色添加到结果集中。
|
||||||
|
5. 根据需要个性化结果集中的每个角色。
|
||||||
|
6. 如果结果集中存在本地admin角色,则添加角色ROLE_ADMINISTRATOR。
|
||||||
|
7. 如果结果集中存在本地组admin角色,则添加角色ROLE_GROUP_ADMIN。
|
||||||
|
|
||||||
|
**用户凭证的身份验证**
|
||||||
|
|
||||||
|
用户/组服务主要用于身份验证。身份验证链[Authentication chain](https://docs.geoserver.org/latest/en/user/security/auth/chain.html#security-auth-chain)中的身份验证提供者可以使用`user/group service`对用户凭证进行身份验证。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**GeoServer默认的安全配置**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 认证
|
||||||
|
|
||||||
|
#### [Authentication chain](https://docs.geoserver.org/latest/en/user/security/auth/chain.html)
|
||||||
|
|
||||||
|
在身份验证中涉及三组GeoServer资源:
|
||||||
|
|
||||||
|
- web 管理界面
|
||||||
|
- OWS服务的认证
|
||||||
|
- REST接口服务的认证
|
||||||
|
|
||||||
|
了解身份验证链有助于解释GeoServer身份验证是如何工作的。身份验证链处理请求并应用某些身份验证机制。认证机制的例子包括:
|
||||||
|
|
||||||
|
- **Username/password**:通过在外部用户数据库中查找用户信息来执行身份验证
|
||||||
|
- **Browser cookie**:通过识别先前发送的浏览器cookie(也称为“Remember Me”)执行身份验证
|
||||||
|
- **LDAP**:对LDAP数据库执行身份验证
|
||||||
|
- **Anonymous**:本质上不执行身份验证,允许请求在没有任何凭据的情况下继续进行
|
||||||
|
|
||||||
|
在给定时间,GeoServer中可能有多个身份验证机制处于活动状态。下图说明了通用请求的流程。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
在将请求分派给适当的服务或处理程序之前,GeoServer首先通过身份验证链过滤请求。请求按顺序传递给链中的每个机制,每个机制都有机会对请求进行身份验证。如果链中的一个机制能够成功地进行身份验证,则请求转到正常处理。否则,不会进一步路由请求,并向用户返回一个授权错误(通常是HTTP 401)。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**过滤链和执行链**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
在GeoServer中,身份验证链实际上由两条链组成:
|
||||||
|
|
||||||
|
1. `filter chain`:它们决定是否需要对请求进行进一步的身份验证;
|
||||||
|
2. `provide chain`:它执行实际的身份验证。
|
||||||
|
|
||||||
|
过滤器链`filter chain`执行各种任务,包括:
|
||||||
|
|
||||||
|
- 从请求中收集用户凭据 credentials ,例如从基本和摘要身份验证头中;
|
||||||
|
- 处理事件,如结束会话(注销),或设置“记住我”浏览器cookie;
|
||||||
|
- 执行会话集成,检测现有会话并在必要时创建新会话;
|
||||||
|
- 调用身份验证提供程序链来执行实际的身份验证。
|
||||||
|
|
||||||
|
过滤器链实际上被处理两次,分别在处理请求之前和之后。
|
||||||
|
|
||||||
|
执行链只关心执行请求的底层身份验证。当过滤器确定需要身份验证时,它由过滤器链调用。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**请求类型&筛选链**
|
||||||
|
|
||||||
|
> 不同的过滤器链可以应用于GeoServer中的每种不同类型的请求。
|
||||||
|
|
||||||
|
这是因为管理员可以配置不同过滤器链的列表,并为每个过滤器链配置匹配规则。只有配置的有序列表的第一个匹配链将应用于任何给定的请求。
|
||||||
|
|
||||||
|
匹配规则可以应用于:
|
||||||
|
|
||||||
|
- HTTP Method (GET, POST, etc.)
|
||||||
|
- 请求的路径部分的一个或多个ANT模式(例如/wms/**);如果指定了多个模式(以逗号分隔),则其中任何一个都将匹配;
|
||||||
|
- 一个可选的正则表达式,用于匹配查询字符串上的一个或多个指定ANT模式的参数;如果路径匹配,也会检查查询字符串是否匹配;正则表达式可以在ANT模式之后使用管道(|)分隔符指定。
|
||||||
|
|
||||||
|
ANT模式支持以下通配符:
|
||||||
|
|
||||||
|
- `?`:匹配一个字符
|
||||||
|
- `*`:匹配零个或多个字符
|
||||||
|
- `**`:匹配路径中的零个或多个'目录'
|
||||||
|
|
||||||
|
查询字符串正则表达式将匹配完整的查询字符串(^和$终止符会自动追加),因此要只匹配其中的一部分,请记住在表达式的前缀和后缀中加上**`.*`**(例如`.*request=getcapabilities.*`。
|
||||||
|
|
||||||
|
规则示例(ANT模式和查询字符串正则表达式)
|
||||||
|
|
||||||
|
| Pattern | Description |
|
||||||
|
| :-------------------------------------------------- | :----------------------------------------------------------- |
|
||||||
|
| `/wms, /wms/**` | simple ANT pattern |
|
||||||
|
| `/wms|.*request=GetMap.*` | ANT pattern and querystring regex to match one parameter |
|
||||||
|
| `/wms|(?=.*request=getmap)(?=.*format=image/png).*` | ANT pattern and querystring regex to match two parameters in any order |
|
||||||
|
| `/wms|(?=.*request=getmap)(?!.*format=image/png).*` | ANT pattern and querystring regex to match one parameters and be sure another one is not matched |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### OWS和REST服务的认证
|
||||||
|
|
||||||
|
OWS和REST服务是**无状态**的,没有固有的“会话”意识,因此这些服务的身份验证方案要求客户端在**每个请求上提供凭据**。也就是说,支持“会话集成”,这意味着如果服务器上已经存在会话(来自并发认证的web管理会话),它将用于身份验证。**此方案允许GeoServer避免OWS和REST服务创建会话的开销。**
|
||||||
|
|
||||||
|
默认GeoServer配置附带了对服务的HTTP基本身份验证的支持。
|
||||||
|
|
||||||
|
典型的认证过程如下:
|
||||||
|
|
||||||
|
1. 用户在不提供任何凭据的情况下发出服务请求
|
||||||
|
|
||||||
|
2. 如果用户正在访问不安全的资源,则正常处理请求
|
||||||
|
|
||||||
|
3. 如果用户正在访问受保护的资源:
|
||||||
|
|
||||||
|
- 将HTTP 401状态码发送回客户端,通常强制客户端提示输入凭据。
|
||||||
|
|
||||||
|
- 然后,在包含适当凭据的情况下重复服务请求,通常在HTTP报头中,就像在Basic Authentication中一样。
|
||||||
|
- 如果用户有足够的权限访问资源,请求将被正常处理,否则,将向客户端返回一个HTTP 404状态码。
|
||||||
|
|
||||||
|
4. 后续请求应包括原始用户凭据。
|
||||||
|
|
||||||
|
OWS服务的认证链如下所示:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
在这个例子中,过滤器链由三个过滤器组成:
|
||||||
|
|
||||||
|
- **Session**:会话,执行会话集成,识别现有会话(但不创建新会话)
|
||||||
|
- **Basic Auth**: 从请求HTTP报头提取基本身份验证凭据
|
||||||
|
- **Anonymous**:处理匿名访问
|
||||||
|
|
||||||
|
提供者链由两个提供者组成:
|
||||||
|
|
||||||
|
- **Root**—Root帐户有一个特殊的“超级用户”提供者。由于很少使用此帐户,因此很少调用此提供程序。
|
||||||
|
- **Username/password**—对用户数据库进行用户名/密码认证
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
1. 匿名请求WMS服务的GetCapabilities
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- 会话筛选器查找现有会话,但没有找到,因此继续处理。
|
||||||
|
|
||||||
|
- 基本认证筛选器在请求中查找基本授权标头,但由于请求是匿名的,因此筛选器没有找到。
|
||||||
|
|
||||||
|
- 最后,Anonymous过滤器以匿名方式执行并验证请求。
|
||||||
|
|
||||||
|
由于GetCapabilities是一个“发现”操作,因此它通常不会被锁定,即使在安全的服务器上也是如此。假设这里是这种情况,则匿名请求成功,并向客户端返回功能响应。不调用提供程序链。
|
||||||
|
|
||||||
|
2. 安全层的匿名WMS GetMap请求
|
||||||
|
|
||||||
|
此示例显示了当WMS客户端为安全层发出匿名GetMap请求时调用的过程。
|
||||||
|
|
||||||
|
该链的执行完全如上所述。
|
||||||
|
|
||||||
|
- 会话筛选器查找现有会话,但没有找到,因此继续处理。
|
||||||
|
- 基本认证筛选器在请求中查找基本授权标头,但由于请求是匿名的,因此筛选器没有找到。
|
||||||
|
- 最后,Anonymous过滤器以匿名方式执行并验证请求。
|
||||||
|
|
||||||
|
然而,在这种情况下,被访问的层是一个受保护的资源,因此GetMap请求的处理失败。服务器返回一个带有HTTP 401状态码的异常,这通常会触发客户端向用户呈现。
|
||||||
|
|
||||||
|
3. 带有用户提供凭据的WMS GetMap请求
|
||||||
|
|
||||||
|
此示例显示了WMS客户机从用户收集凭据并重新发出先前的安全层请求时调用的流程。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- 会话过滤器按照上面描述的方式执行,不执行任何操作。
|
||||||
|
|
||||||
|
- Basic Auth筛选器在请求中查找授权标头,提取其凭据,然后调用提供者链。
|
||||||
|
- 处理转移到执行实际身份验证的Username/password提供程序
|
||||||
|
|
||||||
|
如果凭据具有访问该层所需的特权,则请求的处理将正常继续,GetMap请求成功,返回映射响应。
|
||||||
|
|
||||||
|
如果凭据不够,则将提供HTTP 401状态码,这可能再次触发登录对话框0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 认证的提供者
|
||||||
|
|
||||||
|
在GeoServer中,有下列三种认证方式:
|
||||||
|
|
||||||
|
- 根据user/group服务的用户密码认证
|
||||||
|
- 根据LDAP服务的认证
|
||||||
|
- 根据JDBC连接数据库的认证
|
||||||
|
|
||||||
|
用户名和密码身份验证是默认的身份验证提供程序。
|
||||||
|
|
||||||
|
提供者简单地从传入请求(例如`Basic Authentication`请求)中获取用户名/密码,然后从用户/组服务加载用户信息并验证凭据。
|
||||||
|
|
||||||
|
JDBC身份验证提供者通过JDBC连接到数据库进行身份验证。
|
||||||
|
|
||||||
|
提供者从传入请求中获取用户名/密码,并尝试使用这些凭据创建数据库连接。提供者可以选择使用用户/组服务在成功的身份验证后加载用户信息。在此上下文中,用户/组服务将不用于密码验证,而仅用于角色分配。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 服务安全
|
||||||
|
|
||||||
|
GeoServer支持提供service级别的访问控制,允许锁定服务的操作只有拥有被授权的角色的用户才能操作。在GeoServer中可以将服务大致分为两类:
|
||||||
|
|
||||||
|
- OWS 服务:例如WMS、WFS等
|
||||||
|
- REST 服务
|
||||||
|
|
||||||
|
OWS服务支持为特定服务或该服务中的特定操作全局设置安全访问约束。一些例子包括:
|
||||||
|
|
||||||
|
- 保护整个WFS服务,因此只有经过身份验证的用户才能访问所有WFS操作。
|
||||||
|
- 允许匿名访问只读WFS操作,如GetCapabilities,但保护写操作,如Transaction。
|
||||||
|
- 通过保护所有操作而不向任何用户应用适当的角色来禁用WFS服务。
|
||||||
|
|
||||||
|
OWS服务安全访问规则在名为services的文件中指定。属性,该属性位于GeoServer数据目录中的security目录中。该文件包含将服务操作映射到已定义角色的规则列表。指定规则的语法如下所示
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 1. Key authentication
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 2. CAS
|
||||||
|
|
|
@ -125,6 +125,17 @@ public class Csv2Shape {
|
||||||
* See also the createFeatureType method below for another, more flexible approach.
|
* See also the createFeatureType method below for another, more flexible approach.
|
||||||
*
|
*
|
||||||
* 通过 DataUtilities 创建 FeatureType ,类似定义shp文件的名称、几何类型、属性字段、空间参考等信息。
|
* 通过 DataUtilities 创建 FeatureType ,类似定义shp文件的名称、几何类型、属性字段、空间参考等信息。
|
||||||
|
* createType (String typeName, String typeSpec)
|
||||||
|
* typeName:要素名称
|
||||||
|
* typeSpec:是"name:Type,name2:Type2,..."格式的字符串,用于定义要素的属性字段,其中Type的类型有:
|
||||||
|
* Interger(int、Interger)
|
||||||
|
* Double(Double, double)
|
||||||
|
* String("",String,string)
|
||||||
|
* Geometry(Point,LineString,Polygon,MultiLineString,MultiPolygon,MultiPoint,GeometryCollection)
|
||||||
|
* 还可以是 UUID、Date或是Java的类的全名(含包名)
|
||||||
|
* 示例:
|
||||||
|
* name:"",age:0,geom:Geometry,centroid:Point,url:java.io.URL"
|
||||||
|
* id:String,polygonProperty:Polygon:srid=32615
|
||||||
*/
|
*/
|
||||||
final SimpleFeatureType TYPE =
|
final SimpleFeatureType TYPE =
|
||||||
DataUtilities.createType(
|
DataUtilities.createType(
|
||||||
|
@ -177,6 +188,7 @@ public class Csv2Shape {
|
||||||
featureBuilder.add(point);
|
featureBuilder.add(point);
|
||||||
featureBuilder.add(name);
|
featureBuilder.add(name);
|
||||||
featureBuilder.add(number);
|
featureBuilder.add(number);
|
||||||
|
//buildFeature(id)创建一个要素,ID可以是null,当ID为null时会由builder内部生成
|
||||||
SimpleFeature feature = featureBuilder.buildFeature(null);
|
SimpleFeature feature = featureBuilder.buildFeature(null);
|
||||||
features.add(feature);
|
features.add(feature);
|
||||||
}
|
}
|
||||||
|
@ -190,14 +202,14 @@ public class Csv2Shape {
|
||||||
*/
|
*/
|
||||||
File newFile = getNewShapeFile(file);
|
File newFile = getNewShapeFile(file);
|
||||||
|
|
||||||
// 创建数据存储工厂实例
|
// 创建shapefile类型的数据存储工厂实例
|
||||||
ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
|
ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
|
||||||
|
|
||||||
Map<String, Serializable> params = new HashMap<>();
|
Map<String, Serializable> params = new HashMap<>();
|
||||||
params.put("url", newFile.toURI().toURL());
|
params.put("url", newFile.toURI().toURL());
|
||||||
params.put("create spatial index", Boolean.TRUE);
|
params.put("create spatial index", Boolean.TRUE);
|
||||||
|
|
||||||
// 创建新的数据存储,会输出shp文件
|
// 创建一个空的数据存储
|
||||||
ShapefileDataStore newDataStore = (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);
|
ShapefileDataStore newDataStore = (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -205,6 +217,50 @@ public class Csv2Shape {
|
||||||
* 添加类型描述
|
* 添加类型描述
|
||||||
*/
|
*/
|
||||||
newDataStore.createSchema(TYPE);
|
newDataStore.createSchema(TYPE);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write the features to the shapefile
|
||||||
|
* 创建“create”类型的事务,输出shapefile文件
|
||||||
|
*/
|
||||||
|
Transaction transaction = new DefaultTransaction("create");
|
||||||
|
|
||||||
|
String typeName = newDataStore.getTypeNames()[0];
|
||||||
|
SimpleFeatureSource featureSource = newDataStore.getFeatureSource(typeName);
|
||||||
|
SimpleFeatureType SHAPE_TYPE = featureSource.getSchema();
|
||||||
|
/*
|
||||||
|
* The Shapefile format has a couple limitations:
|
||||||
|
* - "the_geom" is always first, and used for the geometry attribute name
|
||||||
|
* - "the_geom" must be of type Point, MultiPoint, MuiltiLineString, MultiPolygon
|
||||||
|
* - Attribute names are limited in length
|
||||||
|
* - Not all data types are supported (example Timestamp represented as Date)
|
||||||
|
*
|
||||||
|
* Each data store has different limitations so check the resulting SimpleFeatureType.
|
||||||
|
*/
|
||||||
|
System.out.println("SHAPE:" + SHAPE_TYPE);
|
||||||
|
|
||||||
|
if (featureSource instanceof SimpleFeatureStore) {
|
||||||
|
SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
|
||||||
|
/*
|
||||||
|
* SimpleFeatureStore has a method to add features from a
|
||||||
|
* SimpleFeatureCollection object, so we use the ListFeatureCollection
|
||||||
|
* class to wrap our list of features.
|
||||||
|
*/
|
||||||
|
SimpleFeatureCollection collection = new ListFeatureCollection(TYPE, features);
|
||||||
|
featureStore.setTransaction(transaction);
|
||||||
|
try {
|
||||||
|
featureStore.addFeatures(collection);
|
||||||
|
transaction.commit();
|
||||||
|
} catch (Exception problem) {
|
||||||
|
problem.printStackTrace();
|
||||||
|
transaction.rollback();
|
||||||
|
} finally {
|
||||||
|
transaction.close();
|
||||||
|
}
|
||||||
|
System.exit(0); // success!
|
||||||
|
} else {
|
||||||
|
System.out.println(typeName + " does not support read/write access");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Prompt the user for the name and path to use for the output shapefile
|
* Prompt the user for the name and path to use for the output shapefile
|
||||||
|
@ -242,6 +298,43 @@ public class Csv2Shape {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 4. 归纳
|
||||||
|
|
||||||
|
下面是Java中的一些对象与`Geospatial`中的类的对应关系:
|
||||||
|
|
||||||
|
| Java | Geospatial |
|
||||||
|
| :------- | :------------ |
|
||||||
|
| `Object` | `Feature` |
|
||||||
|
| `Class` | `FeatureType` |
|
||||||
|
| `Field` | `Attribute` |
|
||||||
|
| `Method` | `Operation` |
|
||||||
|
|
||||||
|
1. 先创建 `SimpleFeatureType`,就像新建了一个`shpefile`文件一样,定义了shp 文件名和属性字段。
|
||||||
|
2. 通过缓冲流解析文件中的坐标和属性信息,使用`GeometryFactory`几何工厂实例创建几何实例,再通过`featureBuilder`构建要素实例`feature`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### 数据存储
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
`DataStore`代表一份要素数据的物理的源,如`shpaefile`文件、数据库(要素会转为SimpleFeature实例对象)。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### 要素存储
|
||||||
|
|
||||||
|
`FeatureSource` 提供更易于操作feature data要素数据的API,当使用数据源(如shapefile或数据库表)时,您将首先创建一个`DataStore`对象来连接到物理源,然后检索一个`FeatureSource`来处理要素数据。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> `FeatureSource`是用来【读取】要素的类似`View`,而其子类`FeatureStore`是用来【读写】要素的类似于`Table`
|
||||||
|
|
||||||
|
```java
|
||||||
|
File file = ...
|
||||||
|
FileDataStore store = FileDataStoreFinder.getDataStore(file);
|
||||||
|
FeatureSource featureSource = store.getFeatureSource();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -249,6 +342,13 @@ public class Csv2Shape {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### **事务**
|
||||||
|
|
||||||
|
`Transaction` 是有要素存储`FeatureStore`的事务控制器。
|
||||||
|
|
||||||
|
在这个接口的帮助下,可以安全地修改Shapefiles、数据库等。事务还可以在使用锁定`Feature`要素时提供授权。
|
||||||
|
|
||||||
|
所有操作都被认为是在一个事务中进行的。`Transaction.AUTO_COMMIT`用于表示自动提交事务模式。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -100,6 +100,8 @@ GeoTools提供插件来支持额外的数据格式和不同的坐标参考系统
|
||||||
|
|
||||||
### 5.XML
|
### 5.XML
|
||||||
|
|
||||||
|
> 用途:解析和输出sld文件,wfs事务操作
|
||||||
|
|
||||||
为了支持GeoTools中的XML模块,我们以JAR形式捆绑了几个XML模式(以避免每次需要时都需要从Internet下载它们)。此外,这些jar包含一个由Eclipse Modeling Framework生成的Java数据结构。
|
为了支持GeoTools中的XML模块,我们以JAR形式捆绑了几个XML模式(以避免每次需要时都需要从Internet下载它们)。此外,这些jar包含一个由Eclipse Modeling Framework生成的Java数据结构。
|
||||||
|
|
||||||
| JAR | Schema |
|
| JAR | Schema |
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
---
|
||||||
|
title: GeoTools 要素查询
|
||||||
|
date: 2023-12-22
|
||||||
|
author: ac
|
||||||
|
categories:
|
||||||
|
- GIS
|
||||||
|
tags:
|
||||||
|
- GeoTools
|
||||||
|
---
|
||||||
|
|
||||||
|
## GeoTools 要素查询
|
||||||
|
|
||||||
|
### 1.简介
|
||||||
|
|
||||||
|
本文介绍用于查询`DataStores`数据存储(如`shapefiles`、数据库和`Web Feature Servers`)的`Filter API`。
|
||||||
|
|
||||||
|
个人观点:Java关于`JDBC`的一些概念,与`GeoTools`中的类的对应关系如下:
|
||||||
|
|
||||||
|
| GeoTools | JDBC | |
|
||||||
|
| :-------------------- | :-------------------------- | ---------------------- |
|
||||||
|
| `FeatureStore` | `Connection` | 数据库连接 |
|
||||||
|
| `FeatureSource` | `PreparedStatement`|`Table` | 句柄:语句执行者 |
|
||||||
|
| `FeatureCollection` | `ResultSet` | 结果集 |
|
||||||
|
| `AttributeDescriptor` | `ResultSetMetaData` | 结果集元数据(属性列) |
|
||||||
|
| `FeatureIterator` | | |
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```java
|
||||||
|
String shpPath = "D:\\demo\\tutorial\\data\\china\\330000.shp";
|
||||||
|
File shpFile = new File(shpPath);
|
||||||
|
ShapefileDataStore shapefileDataStore = new ShapefileDataStore(shpFile.toURI().toURL());
|
||||||
|
|
||||||
|
// 设置编码,防止属性的中文字符出现乱码
|
||||||
|
shapefileDataStore.setCharset(Charset.forName("UTF-8"));
|
||||||
|
// 获取文件名
|
||||||
|
String typeName = shapefileDataStore.getTypeNames()[0];
|
||||||
|
// 通过文件名在数据存储中找到对应的featuresource(table)
|
||||||
|
FeatureSource featuresource = shapefileDataStore.getFeatureSource(typeName);
|
||||||
|
// 属性查询
|
||||||
|
// Filter filter = CQL.toFilter("adcode = 330100");
|
||||||
|
// Filter filter = CQL.toFilter("name like '%州市%' and childrenNu >8");
|
||||||
|
// 空间查询
|
||||||
|
// CQL.toCQL()
|
||||||
|
Filter filter = CQL.toFilter("BBOX(the_geom, 118.77, 119.626, 28.908, 29.653)");
|
||||||
|
SimpleFeatureCollection features = (SimpleFeatureCollection)featuresource.getFeatures(filter);
|
||||||
|
int count = features.size();
|
||||||
|
System.out.println("查询结果数量:"+count);
|
||||||
|
// 获取当前矢量数据有哪些属性字段值
|
||||||
|
List<AttributeDescriptor> attributes = features.getSchema().getAttributeDescriptors();
|
||||||
|
// 拿到迭代器
|
||||||
|
SimpleFeatureIterator simpleFeatureIterator = features.features();
|
||||||
|
// 遍历每一个要素
|
||||||
|
while(simpleFeatureIterator.hasNext()) {
|
||||||
|
SimpleFeature simpleFeature = simpleFeatureIterator.next();
|
||||||
|
System.out.println("-----------分割线-------------");
|
||||||
|
// java8新特性流api
|
||||||
|
attributes.stream().forEach((a) -> {
|
||||||
|
// 依次读取这个shape中每一个属性值,当然这个属性值,可以处理其它业务
|
||||||
|
System.out.println(a.getLocalName() + ":" + simpleFeature.getAttribute(a.getLocalName()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
从上述可以看到,不管是什么类型的数据源,访问的顺序是先通过`FeatureStore`连接数据源,然后从`store`中获取`FeatureSource`(类似于表),最后通过`cql`类来构建查询条件传入`FeatureSource`的`getFeatures`方法中,可以得到满足条件的要素集`FeatureCollection`。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
1. 对接数据
|
||||||
|
2. 数据录入
|
||||||
|
3. 数据展示
|
||||||
|
4. 资金拨付
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 21 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 30 KiB |
|
@ -0,0 +1,263 @@
|
||||||
|
---
|
||||||
|
title: GeoTools 矢量网格(Vector Grid)
|
||||||
|
date: 2023-12-20
|
||||||
|
author: ac
|
||||||
|
categories:
|
||||||
|
- GIS
|
||||||
|
tags:
|
||||||
|
- GeoTools
|
||||||
|
---
|
||||||
|
|
||||||
|
## GeoTools 矢量网格(Vector Grid)
|
||||||
|
|
||||||
|
### 1.简介
|
||||||
|
|
||||||
|
`GeoTools` 的矢量网格化操作是
|
||||||
|
|
||||||
|
GeoTools矢量网格类使得创建由多边形或线元素组成的矢量网格(也称为网格)变得容易,其中每一个都表示为SimpleFeature。可以使用grids或Lines实用程序类轻松生成简单的网格,而当需要对网格布局和属性进行更多控制时,可以使用较低级别的类。
|
||||||
|
|
||||||
|
> 网格是在内存中构建的,整个网格一次构建。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 2.添加依赖
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.geotools</groupId>
|
||||||
|
<artifactId>gt-grid</artifactId>
|
||||||
|
<version>${geotools.version}</version>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 3. Polygon grids
|
||||||
|
|
||||||
|
Grids实用类提供了创建**矩形**或**六边形**元素网格的方法。
|
||||||
|
|
||||||
|
创建基本网格的最简单方法是使用Grids实用程序类中的静态方法。下面的例子创建了一个经纬度网格,其宽度为10度,用于显示在浙江地图上:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class Le08PolygonGrids {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
String path = "D:\\demo\\tutorial\\data\\china\\zhejiang.shp";
|
||||||
|
Le08PolygonGrids.readShpFile(path);
|
||||||
|
}
|
||||||
|
public static void readShpFile(String shpPath) {
|
||||||
|
File shpFile = new File(shpPath);
|
||||||
|
try {
|
||||||
|
ShapefileDataStore shapefileDataStore = new ShapefileDataStore(shpFile.toURI().toURL());
|
||||||
|
// 设置编码,防止属性的中文字符出现乱码
|
||||||
|
shapefileDataStore.setCharset(Charset.forName("UTF-8"));
|
||||||
|
// 这个typeNamae不传递,默认是文件名称
|
||||||
|
FeatureSource featuresource = shapefileDataStore.getFeatureSource(shapefileDataStore.getTypeNames()[0]);
|
||||||
|
// 读取bbox
|
||||||
|
ReferencedEnvelope bbox =featuresource.getBounds();
|
||||||
|
// 创建grids,0.2度为间隔
|
||||||
|
SimpleFeatureSource grid = Grids.createSquareGrid(bbox, 0.2);
|
||||||
|
|
||||||
|
// Create a map content and add our shapefile to it
|
||||||
|
MapContent map = new MapContent();
|
||||||
|
map.setTitle("polygon grids");
|
||||||
|
// 添加shp到map中
|
||||||
|
Style style = SLD.createSimpleStyle(featuresource.getSchema());
|
||||||
|
Layer layer = new FeatureLayer(featuresource, style);
|
||||||
|
map.addLayer(layer);
|
||||||
|
|
||||||
|
Style styleBox = SLD.createSimpleStyle(grid.getSchema());
|
||||||
|
Layer layerBox = new FeatureLayer(grid, styleBox);
|
||||||
|
map.addLayer(layerBox);
|
||||||
|
|
||||||
|
// Now display the map
|
||||||
|
JMapFrame.showMap(map);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
System.out.println("读取完成!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<img src="./images/image-20231220143856023.png" alt="image-20231220143856023" style="zoom:80%;width:50%" /><img src="./images/image-20231220144056349.png" alt="image-20231220144056349" style="zoom:80%;width:50%" />
|
||||||
|
|
||||||
|
创建的网格由`SimpleFeatures`组成,每个`SimpleFeatures`都有一个最小多边形,即一个由四个角顶点表示的多边形。
|
||||||
|
|
||||||
|
**选择性创建**
|
||||||
|
|
||||||
|
新建一个网格生成类,继承`GridFeatureBuilder`,重写`getCreateFeature`方法来过滤与原数据要素不重叠得网格。
|
||||||
|
|
||||||
|
```java
|
||||||
|
package learning.tools;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.geotools.api.data.SimpleFeatureSource;
|
||||||
|
import org.geotools.api.feature.simple.SimpleFeatureType;
|
||||||
|
import org.geotools.api.filter.Filter;
|
||||||
|
import org.geotools.api.filter.FilterFactory;
|
||||||
|
import org.geotools.factory.CommonFactoryFinder;
|
||||||
|
import org.geotools.geometry.jts.JTSFactoryFinder;
|
||||||
|
import org.geotools.grid.GridElement;
|
||||||
|
import org.geotools.grid.GridFeatureBuilder;
|
||||||
|
import org.geotools.grid.PolygonElement;
|
||||||
|
import org.locationtech.jts.geom.Coordinate;
|
||||||
|
import org.locationtech.jts.geom.Geometry;
|
||||||
|
import org.locationtech.jts.geom.GeometryFactory;
|
||||||
|
|
||||||
|
public class IntersectionBuilder extends GridFeatureBuilder {
|
||||||
|
final FilterFactory ff2 = CommonFactoryFinder.getFilterFactory();
|
||||||
|
final GeometryFactory gf = JTSFactoryFinder.getGeometryFactory();
|
||||||
|
|
||||||
|
final SimpleFeatureSource source;
|
||||||
|
int id = 0;
|
||||||
|
|
||||||
|
public IntersectionBuilder(SimpleFeatureType type, SimpleFeatureSource source) {
|
||||||
|
super(type);
|
||||||
|
this.source = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttributes(GridElement el, Map<String, Object> attributes) {
|
||||||
|
attributes.put("id", ++id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getCreateFeature(GridElement el) {
|
||||||
|
Coordinate c = ((PolygonElement) el).getCenter();
|
||||||
|
Geometry p = gf.createPoint(c);
|
||||||
|
Filter filter = ff2.intersects(ff2.property("the_geom"), ff2.literal(p));
|
||||||
|
boolean result = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
result = !source.getFeatures(filter).isEmpty();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new IllegalStateException(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
package learning;
|
||||||
|
|
||||||
|
import learning.tools.IntersectionBuilder;
|
||||||
|
import org.geotools.api.data.FeatureSource;
|
||||||
|
import org.geotools.api.data.Query;
|
||||||
|
import org.geotools.api.data.SimpleFeatureSource;
|
||||||
|
import org.geotools.api.feature.simple.SimpleFeature;
|
||||||
|
import org.geotools.api.feature.simple.SimpleFeatureType;
|
||||||
|
import org.geotools.api.feature.type.AttributeDescriptor;
|
||||||
|
import org.geotools.api.feature.type.GeometryType;
|
||||||
|
import org.geotools.api.geometry.BoundingBox;
|
||||||
|
import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
|
||||||
|
import org.geotools.api.style.Style;
|
||||||
|
import org.geotools.data.shapefile.ShapefileDataStore;
|
||||||
|
import org.geotools.data.simple.SimpleFeatureCollection;
|
||||||
|
import org.geotools.data.simple.SimpleFeatureIterator;
|
||||||
|
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
|
||||||
|
import org.geotools.geometry.jts.ReferencedEnvelope;
|
||||||
|
import org.geotools.grid.GridElement;
|
||||||
|
import org.geotools.grid.GridFeatureBuilder;
|
||||||
|
import org.geotools.grid.Grids;
|
||||||
|
import org.geotools.grid.PolygonElement;
|
||||||
|
import org.geotools.map.FeatureLayer;
|
||||||
|
import org.geotools.map.Layer;
|
||||||
|
import org.geotools.map.MapContent;
|
||||||
|
import org.geotools.referencing.crs.DefaultGeographicCRS;
|
||||||
|
import org.geotools.styling.SLD;
|
||||||
|
import org.geotools.swing.JMapFrame;
|
||||||
|
import org.locationtech.jts.geom.Geometry;
|
||||||
|
import org.locationtech.jts.geom.Polygon;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author ac
|
||||||
|
* @date 2023/12/20 13:54
|
||||||
|
*/
|
||||||
|
public class Le08PolygonGrids {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
String path = "D:\\demo\\tutorial\\data\\china\\zhejiang.shp";
|
||||||
|
Le08PolygonGrids.readShpFile(path);
|
||||||
|
}
|
||||||
|
public static void readShpFile(String shpPath) {
|
||||||
|
File shpFile = new File(shpPath);
|
||||||
|
try {
|
||||||
|
ShapefileDataStore shapefileDataStore = new ShapefileDataStore(shpFile.toURI().toURL());
|
||||||
|
// 设置编码,防止属性的中文字符出现乱码
|
||||||
|
shapefileDataStore.setCharset(Charset.forName("UTF-8"));
|
||||||
|
// 这个typeNamae不传递,默认是文件名称
|
||||||
|
FeatureSource featuresource = shapefileDataStore.getFeatureSource(shapefileDataStore.getTypeNames()[0]);
|
||||||
|
// 读取bbox
|
||||||
|
ReferencedEnvelope bbox =featuresource.getBounds();
|
||||||
|
Double sideLen = 0.2;
|
||||||
|
|
||||||
|
// 创建网格
|
||||||
|
// 新建一份面要素shp,名叫grid,有两个字段id,the_geom
|
||||||
|
SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder();
|
||||||
|
tb.setName("grid");
|
||||||
|
tb.add(
|
||||||
|
GridFeatureBuilder.DEFAULT_GEOMETRY_ATTRIBUTE_NAME,
|
||||||
|
Polygon.class,
|
||||||
|
bbox.getCoordinateReferenceSystem());
|
||||||
|
tb.add("id", Integer.class);
|
||||||
|
SimpleFeatureType TYPE = tb.buildFeatureType();
|
||||||
|
|
||||||
|
// Build the grid the custom feature builder class
|
||||||
|
GridFeatureBuilder builder = new IntersectionBuilder(TYPE, (SimpleFeatureSource)featuresource);
|
||||||
|
SimpleFeatureSource grid = Grids.createHexagonalGrid(bbox, sideLen, -1, builder);
|
||||||
|
|
||||||
|
// Create a map content and add our shapefile to it
|
||||||
|
MapContent map = new MapContent();
|
||||||
|
map.setTitle("polygon grids");
|
||||||
|
// 添加shp到map中
|
||||||
|
Style style = SLD.createSimpleStyle(featuresource.getSchema());
|
||||||
|
Layer layer = new FeatureLayer(featuresource, style);
|
||||||
|
map.addLayer(layer);
|
||||||
|
|
||||||
|
Style styleBox = SLD.createSimpleStyle(grid.getSchema());
|
||||||
|
Layer layerBox = new FeatureLayer(grid, styleBox);
|
||||||
|
map.addLayer(layerBox);
|
||||||
|
|
||||||
|
// Now display the map
|
||||||
|
JMapFrame.showMap(map);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
System.out.println("读取完成!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 4.属性渲染
|
||||||
|
|
||||||
|
到目前为止,没有一个示例需要为矢量网格指定 feature type特征类型。相反,我们用两个属性创建了一个默认的 feature type特性类型:
|
||||||
|
|
||||||
|
- ‘element’ (the Polygon instance)
|
||||||
|
- ‘id’ (a sequential integer ID value.
|
||||||
|
|
||||||
|
当然,您也可以提供自己的特性类型,以便将其他属性与网格元素关联起来。要做到这一点,你需要重写GridFeatureBuilder类的setAttributes方法。下面的例子创建了一个带有' color '属性的特性类型。然后根据网格中每个六边形元素的位置设置颜色值:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 160 KiB |
After Width: | Height: | Size: 34 KiB |
|
@ -0,0 +1,594 @@
|
||||||
|
### Spring Security
|
||||||
|
---
|
||||||
|
>Spring Security 基于Spring框架提供的一套Web应用安全性的完整解决方案。
|
||||||
|
|
||||||
|
#### 1.简介
|
||||||
|
SpingSecurity框架是Spring开源社区的顶级项目。
|
||||||
|
Spring开源社区顶级项目都提供了Spring Boot启动器(`spring-boot-starter-security`),做快速启动、环境装配。
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>2.0.4.RELEASE</version>
|
||||||
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
|
</parent>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
```
|
||||||
|
|
||||||
|
项目中增加security启动器依赖。不需要任何配置和特殊编码,自动提供一个登陆处理逻辑。
|
||||||
|
|
||||||
|
**默认登陆方式**
|
||||||
|
默认情况下,出/login请求地址可以正常访问外,其他所有请求地址,在未登陆时,自动跳转到/login登陆页面。
|
||||||
|
默认情况下,security框架启动时,会在控制台日至终输出一个UUID字符串,此字符串是security框架默认生成的登陆密码,用户名默认为user。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**配置文件方式**
|
||||||
|
security框架提供了配置文件设置默认用户和密码的方式。(可以在不编写任何代码的前提下,定义登陆用户名和密码)
|
||||||
|
|
||||||
|
````yaml
|
||||||
|
spring:
|
||||||
|
security:
|
||||||
|
user:
|
||||||
|
name: qiusj
|
||||||
|
password: qiusj123
|
||||||
|
````
|
||||||
|
|
||||||
|
#### 2. 自定义认证流程
|
||||||
|
为了提供不同用户的权限,一般都会基于`RBAC`(**Role-Based Access Control**)模型设计数据库。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> 每个用户,拥有哪些角色,角色拥有哪些操作?可以访问哪些数据?
|
||||||
|
|
||||||
|
**设计表**
|
||||||
|
|
||||||
|
权限表`tb_permission`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
CREATE TABLE IF NOT EXISTS public.tb_permission
|
||||||
|
(
|
||||||
|
id smallint NOT NULL DEFAULT nextval('tb_permission_id_seq'::regclass),
|
||||||
|
name character varying(32) COLLATE pg_catalog."default",
|
||||||
|
url character varying(255) COLLATE pg_catalog."default" DEFAULT NULL::character varying,
|
||||||
|
parent_id integer,
|
||||||
|
type character varying(24) COLLATE pg_catalog."default",
|
||||||
|
permit character varying(128) COLLATE pg_catalog."default",
|
||||||
|
remark text COLLATE pg_catalog."default",
|
||||||
|
CONSTRAINT tb_permission_pkey PRIMARY KEY (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
TABLESPACE pg_default;
|
||||||
|
ALTER TABLE IF EXISTS public.tb_permission OWNER to postgres;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN public.tb_permission.name IS '权限名称';
|
||||||
|
COMMENT ON COLUMN public.tb_permission.url IS '请求地址';
|
||||||
|
COMMENT ON COLUMN public.tb_permission.parent_id IS '父权限主键';
|
||||||
|
COMMENT ON COLUMN public.tb_permission.type IS '权限类型,M-菜单,A-子菜单,U-普通请求';
|
||||||
|
COMMENT ON COLUMN public.tb_permission.permit IS '权限字符串描述';
|
||||||
|
COMMENT ON COLUMN public.tb_permission.remark IS '描述';
|
||||||
|
```
|
||||||
|
|
||||||
|
角色表:`tb_role`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
CREATE TABLE IF NOT EXISTS public.tb_role
|
||||||
|
(
|
||||||
|
id smallint NOT NULL DEFAULT nextval('tb_role_id_seq'::regclass),
|
||||||
|
name character varying(32) COLLATE pg_catalog."default",
|
||||||
|
remark text COLLATE pg_catalog."default",
|
||||||
|
CONSTRAINT tb_role_pkey PRIMARY KEY (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
TABLESPACE pg_default;
|
||||||
|
ALTER TABLE IF EXISTS public.tb_role OWNER to postgres;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN public.tb_role.name IS '角色名称';
|
||||||
|
COMMENT ON COLUMN public.tb_role.remark IS '角色描述';
|
||||||
|
```
|
||||||
|
|
||||||
|
用户表:`tb_user`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
CREATE TABLE IF NOT EXISTS public.tb_user
|
||||||
|
(
|
||||||
|
id smallint NOT NULL DEFAULT nextval('tb_user_id_seq'::regclass),
|
||||||
|
name character varying(32) COLLATE pg_catalog."default",
|
||||||
|
username character varying(32) COLLATE pg_catalog."default",
|
||||||
|
password character varying(128) COLLATE pg_catalog."default",
|
||||||
|
remark text COLLATE pg_catalog."default",
|
||||||
|
CONSTRAINT tb_user_pkey PRIMARY KEY (id)
|
||||||
|
)
|
||||||
|
|
||||||
|
TABLESPACE pg_default;
|
||||||
|
ALTER TABLE IF EXISTS public.tb_user OWNER to postgres;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN public.tb_user.name IS '姓名';
|
||||||
|
COMMENT ON COLUMN public.tb_user.username IS '用户名';
|
||||||
|
COMMENT ON COLUMN public.tb_user.password IS '密码';
|
||||||
|
COMMENT ON COLUMN public.tb_user.remark IS '描述';
|
||||||
|
```
|
||||||
|
|
||||||
|
角色权限关系表:`tb_role_permission`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
CREATE TABLE IF NOT EXISTS public.tb_role_permission
|
||||||
|
(
|
||||||
|
role_id integer,
|
||||||
|
permission_id integer
|
||||||
|
)
|
||||||
|
|
||||||
|
TABLESPACE pg_default;
|
||||||
|
|
||||||
|
ALTER TABLE IF EXISTS public.tb_role_permission OWNER to postgres;
|
||||||
|
```
|
||||||
|
|
||||||
|
用户角色关系表:`tb_user_role`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
CREATE TABLE IF NOT EXISTS public.tb_user_role
|
||||||
|
(
|
||||||
|
user_id integer,
|
||||||
|
role_id integer
|
||||||
|
)
|
||||||
|
|
||||||
|
TABLESPACE pg_default;
|
||||||
|
|
||||||
|
ALTER TABLE IF EXISTS public.tb_user_role OWNER to postgres;
|
||||||
|
```
|
||||||
|
|
||||||
|
测试数据:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
INSERT INTO public.tb_permission(
|
||||||
|
name, url, parent_id, type, permit, remark)
|
||||||
|
VALUES
|
||||||
|
( '登陆', '/login', 0, 'U', 'login:login', '登陆权限'),
|
||||||
|
( '进入登陆页', '/login', 0, 'U', 'login:toLogin', '进入登录页面权限'),
|
||||||
|
( '退出', '/logout', 0, 'U', 'logout', '退出权限'),
|
||||||
|
( '注册', '/register', 0, 'U', 'reg:register', '注册权限'),
|
||||||
|
( '进入注册界面', '/toRegister', 0, 'U', 'reg:toRegister', '进入注册页面权限'),
|
||||||
|
( '用户管理', '', 0, 'M', 'user:manager', '用户管理权限'),
|
||||||
|
( '用户查询', '/user/list', 6, 'U', 'user:list', '用户查询权限'),
|
||||||
|
( '用户新增', '/user/add', 6, 'U', 'user:add', '用户新增权限'),
|
||||||
|
( '进入用户新增界面', '/user/ToAdd', 6, 'U', 'user:toAdd', '进入用户新增界面权限'),
|
||||||
|
( '用户修改', '/user/modify', 6, 'U', 'user;modify', '用户修改权限'),
|
||||||
|
( '进入用户修改', '/user/ToModify', 6, 'U', 'user:ToModify', '进入用户修改权限'),
|
||||||
|
( '用户删除', '/user/remove', 6, 'U', 'user:remove', '用户删除权限'),
|
||||||
|
( '进入主页面', '/main', 0, 'U', 'reg:toRegister', '进入主页面权限');
|
||||||
|
INSERT INTO public.tb_role( name, remark)
|
||||||
|
VALUES
|
||||||
|
('超级管理员','全部权限'),
|
||||||
|
('普通用户','基础权限');
|
||||||
|
|
||||||
|
INSERT INTO public.tb_user(
|
||||||
|
name, username, password, remark)
|
||||||
|
VALUES ('超级管理员','admin','123','超级管理员'),
|
||||||
|
('普通用户','guest','guest','普通用户');
|
||||||
|
|
||||||
|
INSERT INTO public.tb_role_permission(
|
||||||
|
role_id, permission_id)
|
||||||
|
VALUES (1,1),(1,2),(1,3),(1,4),(1,5),(1,6),(1,7),(1,8),(1,9),(1,10),(1,11),(1,12),(1,13),
|
||||||
|
(2,1),(2,2),(2,3),(2,4),(2,5),(2,13),;
|
||||||
|
|
||||||
|
insert into tb_user_role(user_id,role_id)
|
||||||
|
values(1,1),(2,2);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**密码解析器 **
|
||||||
|
|
||||||
|
Security框架定义的接口 PasswordEncoder,**Security框架强制要求**,必须在Spring容器中存在PasswordEncoder类型对象,且对象唯一。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MyPasswordEncoder implements PasswordEncoder {
|
||||||
|
/**
|
||||||
|
* 密码加密方法
|
||||||
|
* @param charSequence
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String encode(CharSequence charSequence) {
|
||||||
|
return charSequence.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验密码明文和密文是否相同的方法
|
||||||
|
* @param charSequence 明文密码,登陆时客户端传递过来的
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean matches(CharSequence charSequence, String s) {
|
||||||
|
// 先使用encode方法加密明文,在与加密后的密文对比
|
||||||
|
return encode(charSequence).equals(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Configuration
|
||||||
|
public class MySecurityConfiguration {
|
||||||
|
/**
|
||||||
|
* 创建一个PasswordEncoder对象
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder(){
|
||||||
|
return new MyPasswordEncoder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**定义登陆服务类型**
|
||||||
|
|
||||||
|
Security框架定义了一个**登陆服务接口**,类型是:UserDetailsService。
|
||||||
|
|
||||||
|
> 此接口必须自定义实现类,自定义实现类型的对象必须被spring容器管理且唯一。
|
||||||
|
|
||||||
|
此接口中定义了唯一的登陆服务方法,方法是:
|
||||||
|
|
||||||
|
`UserDetails loadUserByUsername(String username) throws UsernameNotFoundException`;
|
||||||
|
此方法用于查询登陆用户,仅需要根据用户名查询用户对象并返回。
|
||||||
|
如果用户查询没有结果,跑出异常UsernameNotFoundException。
|
||||||
|
方法返回类型是Security框架定义的接口类型,是`UserDetails`。
|
||||||
|
接口中定义了若干方法,重要方法是,获取用户对象和获取用户权限列表:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public interface UserDetails extends Serializable {
|
||||||
|
//返回登陆用户的权限列表
|
||||||
|
Collection<? extends GrantedAuthority> getAuthorities();
|
||||||
|
|
||||||
|
//返回登陆用户密码,是服务器保存的密文密码,编写友好的实现,一般都会隐藏密码结果
|
||||||
|
String getPassword();
|
||||||
|
|
||||||
|
// 返回用户名,身份 principal
|
||||||
|
String getUsername();
|
||||||
|
|
||||||
|
// 返回用户是否过期,true未过期
|
||||||
|
boolean isAccountNonExpired();
|
||||||
|
|
||||||
|
// 返回用户是否锁定,true未锁定
|
||||||
|
boolean isAccountNonLocked();
|
||||||
|
|
||||||
|
// 返回账户凭证是否过期,true未过期
|
||||||
|
boolean isCredentialsNonExpired();
|
||||||
|
|
||||||
|
// 返回登陆用户是否可用
|
||||||
|
boolean isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Component
|
||||||
|
public class MyUserDetailsServiceImpl implements UserDetailsService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
UserMapper userMapper;
|
||||||
|
/**
|
||||||
|
* 根据用户名查询用户对象
|
||||||
|
* @param s
|
||||||
|
* @return
|
||||||
|
* @throws UsernameNotFoundException
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
|
||||||
|
User user = userMapper.selectByUsername(s);
|
||||||
|
if(user == null){
|
||||||
|
throw new UsernameNotFoundException("用户名或密码错误!");
|
||||||
|
}
|
||||||
|
// 返回 UserDetials对象
|
||||||
|
org.springframework.security.core.userdetails.User result = new org.springframework.security.core.userdetails.User(
|
||||||
|
s,
|
||||||
|
user.getPassword(),// 登陆用户的密码,是服务端的密文
|
||||||
|
AuthorityUtils.NO_AUTHORITIES//暂时用框架提供的无权限集合
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 3.简述Security认证流程
|
||||||
|
下述所有流程,是基于Security框架默认设置的前提下。提供自定义认证流程后
|
||||||
|
|
||||||
|
1. 请求当前项目的任何地址,出/login外。框架检查是否已登陆,未登录时,自动进入登陆页面。重定向到/login。已登陆,正常访问。
|
||||||
|
/login get请求,进入登陆页面
|
||||||
|
/login post请求,登陆
|
||||||
|
|
||||||
|
2. 填写登陆表单(用户名和密码),点击登陆,访问/login,post请求
|
||||||
|
|
||||||
|
3. Security 框架负责收集请求参数,用户名和密码。
|
||||||
|
|
||||||
|
4. Security 框架从Spring容器中,根据类型获取bean对象,UserDetailsService类型的对象。ApplicationContext.getBean(UserDetailsService.class)
|
||||||
|
|
||||||
|
5. Security框架调用方法,loadUserByUsername。根据用户名查询用户对象。
|
||||||
|
|
||||||
|
6. 如果loadUserByUsername 抛出异常,止咳重定向到/login?error,登陆失败。用户名不存在。
|
||||||
|
|
||||||
|
7. 如果loadUserByUsername 未抛出异常,则校验返回值中保存的密码和请求参数密码是否匹配。
|
||||||
|
|
||||||
|
获取Spring容器中的PasswordEncoder类型对象,调用方法matches比较密码。
|
||||||
|
|
||||||
|
如果密码比较结果为true,登陆成功。
|
||||||
|
|
||||||
|
如果密码比较结果为false,登陆失败,重定向到/login?error。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 4. 认证安全强化
|
||||||
|
|
||||||
|
> 修改密码解析器的实现对象。
|
||||||
|
|
||||||
|
在数据库中保存加密后的密码数据,并修改密码解析器对象。
|
||||||
|
|
||||||
|
Security框架,提供若干密码解析器实现类型,其中`BCryptPasswordEnder` 叫强散列加密,可以保证相同明文,多次加密后,密文有相同的散列数据,而不是相同的结果。匹配时,是基于相同的散列数据做的匹配。
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class TestBCryptPE {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
|
||||||
|
String pwd = "123";
|
||||||
|
String m1 = bCryptPasswordEncoder.encode(pwd);
|
||||||
|
String m2 = bCryptPasswordEncoder.encode(pwd);
|
||||||
|
String m3 = bCryptPasswordEncoder.encode(pwd);
|
||||||
|
System.out.println(m1);
|
||||||
|
System.out.println(m2);
|
||||||
|
System.out.println(m3);
|
||||||
|
System.out.println(bCryptPasswordEncoder.matches(pwd,m1));//true
|
||||||
|
System.out.println(bCryptPasswordEncoder.matches(pwd,m2));//true
|
||||||
|
System.out.println(bCryptPasswordEncoder.matches(pwd,m3));//true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
强化密码解析器
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Configuration
|
||||||
|
public class MySecurityConfiguration {
|
||||||
|
/**
|
||||||
|
* 创建一个PasswordEncoder对象,更换为BCryptPasswordEncoder实例
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder(){
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### 5. 自定义配置
|
||||||
|
|
||||||
|
使用自定义配置信息,覆盖Security内置的默认信息。
|
||||||
|
|
||||||
|
低版本(springboot2 + javaEE + security 5)
|
||||||
|
|
||||||
|
使用Configuration实现配置。继承`Security`框架提供的适配器类型`SecurityConfigurerAdapter`。该类型需要提供泛型,`SecurityConfigurerAdapter<DefaultSecurityFilterChain,HttpSecurity>`其中:
|
||||||
|
|
||||||
|
- DefaultSecurityFilterChain:用于提供Servlet技术中的过滤链。
|
||||||
|
- HttpSecurity:用于提供Seurity框架中的配置处理。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
高版本(Spring Boot3 + jakartaEE + security 6)
|
||||||
|
|
||||||
|
Spring Boot3 + jakartaEE + security 6版本开发时,修改了自定义配置策略。从springboot2 + javaEE + security 5版本的`WebSecurityConfigure`自动装配策略,改为`Configuration + Bean` 对象配置策略。
|
||||||
|
|
||||||
|
要求:
|
||||||
|
|
||||||
|
- 定义Configuration配置类型,定义Bean对象管理方法。方法创建`SecurityFilterChain`类型对象。Security框架,自动在spring容器中,创建一个`HttpSecurity`类型的对象,可以通过方法参数传递个创建SecurityFilterChain对象的方法。
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Configuration
|
||||||
|
public class MySecurityConfiguration {
|
||||||
|
/**
|
||||||
|
* 创建一个PasswordEncoder对象
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder(){
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity security) throws Exception {
|
||||||
|
return security.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
SecurityFilterChain对象,不需要手工new,可以通过HttpSecurity对象中的方法build构建。
|
||||||
|
|
||||||
|
Security框架官方推荐,在提供Security配置对象的类型上,增加注解。EnableWebSecurity,让Security框架一定生效。
|
||||||
|
|
||||||
|
HttpSecurity对象中,存在若干方法,可以提供配置内容,包括登陆认证配置、授权配置、CSRF配置等
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### JWT
|
||||||
|
|
||||||
|
> JWT(JSON Web Token)是JSON格式被加密的字符串
|
||||||
|
|
||||||
|
**1. 无状态登陆**
|
||||||
|
**1.1 什么是有状态**
|
||||||
|
有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份处理请求,例如:Tomcat中的Session(用户登录后,我们把用户的信息保存在服务端Session中,并且给用户一个cookie值,记录session,然后下次请求用户带上cookie值,浏览器自动完成,这样服务端能识别到对应的Session 从而找到用户信息)这不足之处在于:
|
||||||
|
|
||||||
|
* 服务端保存大量的数据
|
||||||
|
* 服务端保存的用户状态,不支持集群化部署
|
||||||
|
|
||||||
|
**1.2 什么是无状态**
|
||||||
|
微服务集群中的每个服务,对外提供的都是使用RESTful风格的接口,而RESTful风格的一个最重要的规范就是服务的无状态,即
|
||||||
|
* 服务端不保存任何客户端请求信息
|
||||||
|
* 客户端的每次请求必须具备自我描述信息,通过这些信息识别客户端身份
|
||||||
|
|
||||||
|
这种无状态的好处在于:
|
||||||
|
* 客户端请求不依赖服务端的信息,多次请求不需要必须访问到同一台服务器
|
||||||
|
* 服务端的集群和状态对客户端透明
|
||||||
|
* 服务端可以任意的迁移和伸缩(可以方便进行集群化部署)
|
||||||
|
* 减少服务端存储压力
|
||||||
|
|
||||||
|
**1.3 如何实现无状态**
|
||||||
|
无状态的登陆流程:
|
||||||
|
* 首先客户端发送用户名密码到服务端进行认证
|
||||||
|
* 认证通过后,服务端将用户信息加密并且编码成一个token,返回客户端
|
||||||
|
* 以后客户端每次发送请求,都需要携带认证的token
|
||||||
|
* 服务端对客户端发送来的token进行解密,判断是否有效,并且获取用户登陆信息
|
||||||
|
|
||||||
|
**1.4 JWT(JSON Web Token)**
|
||||||
|
JWT是一种JSON风格的轻量级的授权和身份认证规范,可实现无状态、分布式的web应用授权。
|
||||||
|
JWT作为一种规范,并没有和某种语言绑定在一起,常用的Java实现是Github上的开源项目jjwt
|
||||||
|
|
||||||
|
**1.4.1 JWT数据格式**
|
||||||
|
JWT包含三部分数据:
|
||||||
|
1. Header :通常包含两部分信息(声明类型、加密算法)
|
||||||
|
2. Payload:载荷,即有效数据
|
||||||
|
3. Signature:签名
|
||||||
|
荷载部分在官方文档中(RFC7519)给了7个示例信息
|
||||||
|
* **iss(issuer):签发人**
|
||||||
|
* **exp(expiration time):token过期时间**
|
||||||
|
* **sub(subject):主题**
|
||||||
|
* **aud(audience):受众**
|
||||||
|
* **nbf(Not Before):生效时间**
|
||||||
|
* **lat(issued At):签发时间**
|
||||||
|
* **jti(JWT ID):编号**
|
||||||
|
|
||||||
|
**签名**是整个数据的认证信息,是根据前两个步骤的数据,再加上服务的密钥secret(密钥保存在服务端,不能泄露给客户端),通过header中配置的加密算法生成,用于校验整个数据的完整和可靠性
|
||||||
|
生成的数据格式如下:
|
||||||
|

|
||||||
|
|
||||||
|
>这里数据通过 . 隔开成三部分
|
||||||
|
|
||||||
|
**1.4.2 JWT交互流程**
|
||||||
|
流程图:
|
||||||
|

|
||||||
|
|
||||||
|
步骤翻译:
|
||||||
|
1. 应用程序或客户端向授权服务器请求授权
|
||||||
|
2. 获取到授权后,授权服务器会向应用程序返回访问令牌
|
||||||
|
3. 应用程序使用访问令牌来访问受保护的资源
|
||||||
|
|
||||||
|
因为JWT签发的token中已经包含了用户的身份信息。并且每次请求都会携带,这样服务端就无需保存用户信息,
|
||||||
|
|
||||||
|
**1.4.3 JWT存在的问题**
|
||||||
|
1. 续签问题,
|
||||||
|
2. 注销问题:由于服务端不再保存用户信息。所以一般可以通过修改secret来实现注销,服务端secret修改后,已经颁发的为过期的token就会认证失败,进而实现注销
|
||||||
|
3. 密码重置:密码重置后,原本的token依然可以进行访问系统,这时候也需要强制修改secret
|
||||||
|
4. 基于2和3,一般建议不同用户取不同的secret
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 2. Spring Security
|
||||||
|
**2.1 Spring Security 中的核心概念**
|
||||||
|
* AuthenticationManager:用户认证的管理类,所有的认证请求都会通过提交一个token给 <font color="red">AuthenticationManager</font> 和
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
事件循环
|
||||||
|
|
||||||
|
闭包:应用场景,防抖,节流
|
||||||
|
|
||||||
|
深拷贝实现方式()
|
||||||
|
|
||||||
|
number boolean string null undefind object Function Array symbol Set Map Collection list
|
||||||
|
|
||||||
|
XHR
|
||||||
|
|
||||||
|
xmlHttpRequest
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let const 工具IDE babel
|
||||||
|
|
||||||
|
Promise
|
||||||
|
|
||||||
|
fetch
|
|
@ -1,3 +1,10 @@
|
||||||
|
> ts最大的意义就是避免你错写,漏写,能基本上屏蔽掉你的第几错误。
|
||||||
|
>
|
||||||
|
> 应用场景:
|
||||||
|
>
|
||||||
|
> 1. 编写一些公用方法和全局配置对象,用于提醒使用者别传错参数。如:`API`请求参数对象`param`的类型声明,规范参数和响应结果`result`的类型限制。
|
||||||
|
> 2. 编写组件的时候用于提示使用者有没有写错`props`。
|
||||||
|
|
||||||
## 初识TypeScript
|
## 初识TypeScript
|
||||||
|
|
||||||
> Typed JavaScript at Any Scale,添加了类型系统的JavaScript,适用于任何规模的项目。
|
> Typed JavaScript at Any Scale,添加了类型系统的JavaScript,适用于任何规模的项目。
|
||||||
|
@ -13,7 +20,7 @@ TypeScript是**静态类型**
|
||||||
类型系统按照【类型检查的时机】来分类:
|
类型系统按照【类型检查的时机】来分类:
|
||||||
|
|
||||||
- 动态类型是指在运行时才会进行类型检查
|
- 动态类型是指在运行时才会进行类型检查
|
||||||
- 静态类型是指编译阶段就能确定每个变量的类型
|
- **静态类型是指编译阶段就能确定每个变量的类型**
|
||||||
|
|
||||||
> 类型检查,当检查到调用的是不属于该对象的方法时就会报错,如:1.split()
|
> 类型检查,当检查到调用的是不属于该对象的方法时就会报错,如:1.split()
|
||||||
|
|
||||||
|
@ -30,7 +37,7 @@ TypeScript是弱类型
|
||||||
console.log(1+'1');
|
console.log(1+'1');
|
||||||
```
|
```
|
||||||
|
|
||||||
|
TypeScript 是完全兼容 JavaScript 的,它不会修改 JavaScript 运行时的特性,所以**它们都是弱类型**。
|
||||||
|
|
||||||
## 安装TypeScript
|
## 安装TypeScript
|
||||||
|
|
||||||
|
@ -56,7 +63,7 @@ VS Code自动编译
|
||||||
|
|
||||||
## 基础
|
## 基础
|
||||||
|
|
||||||
**类型声明** : 在定义变量时,用`:` 加类型的形式。变量只能存储指定类型的值。
|
**类型声明** : 在定义变量时,用`:` 加类型的形式。**变量只能存储指定类型的值。**
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
let num:number = 11;
|
let num:number = 11;
|
||||||
|
@ -64,19 +71,23 @@ let num:number = 11;
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
|
### 原始数据类型
|
||||||
|
|
||||||
原始数据类型:number、string、boolean、null、undefined、Symbol(ES6)、BigInt(ES10)
|
原始数据类型:number、string、boolean、null、undefined、Symbol(ES6)、BigInt(ES10)
|
||||||
|
|
||||||
> undefined/null可以作为其他类型的子类型赋值给其他类型,可以理解成面向对象的多态,向上转型:
|
> undefined/null可以作为其他类型的子类型赋值给其他类型,可以理解成面向对象的多态,向上转型:
|
||||||
>
|
>
|
||||||
> let num:number = null; ==> let 变量名:父类 = 子类型的值
|
> let num:number = null; ==> let 变量名:父类 = 子类型的值
|
||||||
|
|
||||||
|
### 任意值
|
||||||
|
|
||||||
任意值:any,当变量定义的时候只有声明没有赋值的情况。
|
任意值:any,当变量定义的时候只有声明没有赋值的情况。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
let abc; ==> let abc:any;
|
let abc; ==> let abc:any;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 空类型
|
||||||
|
|
||||||
空类型:void,表示没有任何返回值的函数。
|
空类型:void,表示没有任何返回值的函数。
|
||||||
|
|
||||||
|
@ -86,17 +97,17 @@ function fun1():void{
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 类型推论
|
||||||
|
|
||||||
类型推论(Type Inference):没有明确的指定类型,有赋值。
|
类型推论(Type Inference):没有明确的指定类型,有赋值。
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// 定义变量时,直接赋值,则定义类型为对应的类型
|
// 定义变量时,直接赋值,则定义类型为对应的类型,会根据赋值的类型推断变量的类型
|
||||||
let ac = 123;
|
let ac = 123;
|
||||||
// ac = 'w';//报错
|
// ac = 'w';//报错
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 联合类型
|
||||||
|
|
||||||
联合类型(Union Types):标识取值可以为多种类型中的一种。使用`|`分隔每种类型。
|
联合类型(Union Types):标识取值可以为多种类型中的一种。使用`|`分隔每种类型。
|
||||||
|
|
||||||
|
@ -107,7 +118,7 @@ f=123;//再次赋值,走类型推断,给变量定义一个类型,就可以
|
||||||
|
|
||||||
> **访问联合类型的属性或方法只能访问共有的属性和方法。**
|
> **访问联合类型的属性或方法只能访问共有的属性和方法。**
|
||||||
|
|
||||||
|
### 对象的类型
|
||||||
|
|
||||||
对象的类型-接口:接口(interfaces)定义了对象的`形状`,是对象行为的抽象,具体的行为有类(classes)去实现(implement)。可以理解成一中约束。
|
对象的类型-接口:接口(interfaces)定义了对象的`形状`,是对象行为的抽象,具体的行为有类(classes)去实现(implement)。可以理解成一中约束。
|
||||||
|
|
||||||
|
@ -174,7 +185,7 @@ let tom: Person = {
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 数组类型
|
||||||
|
|
||||||
数组类型:多种定义方式
|
数组类型:多种定义方式
|
||||||
|
|
||||||
|
@ -199,32 +210,50 @@ let fibonacci: NumberArray = [1, 1, 2, 3, 5];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
函数类型
|
### 函数类型
|
||||||
|
|
||||||
```javascript
|
- 函数声明
|
||||||
interface ISearchFunc{
|
|
||||||
|
```javascript
|
||||||
|
// ts,函数声明,命名函数
|
||||||
|
function add(a:number,bnumber):number{
|
||||||
|
return a+b;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 函数表达式
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ts,函数表达式,匿名函数
|
||||||
|
let sum = function(a:string,bstring):boolean{
|
||||||
|
return a.search(b) !== -1;
|
||||||
|
}
|
||||||
|
// ts,完整写法
|
||||||
|
// 在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
|
||||||
|
let add:(a:number,b:number)=>number = function(a:number,b:number):number{
|
||||||
|
return a.search(b) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- 接口定义函数的形状
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
interface ISearchFunc{
|
||||||
// (参数:类型,...):返回值类型
|
// (参数:类型,...):返回值类型
|
||||||
(a;string,b:string):boolean;
|
(a;string,b:string):boolean;
|
||||||
}
|
}
|
||||||
const fun1:ISearchFunc = function(a:string,bstring):boolean{
|
const fun1:ISearchFunc = function(a:string,bstring):boolean{
|
||||||
return a.search(b) !== -1;
|
return a.search(b) !== -1;
|
||||||
}
|
}
|
||||||
console.log(fun1('123','1'));
|
console.log(fun1('123','1'));
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
// ts,函数声明,命名函数
|
|
||||||
function add(a:number,bnumber):number{
|
|
||||||
return a+b;
|
|
||||||
}
|
|
||||||
// ts,函数表达式,匿名函数
|
|
||||||
let sum = function(a:string,bstring):boolean{
|
|
||||||
return a.search(b) !== -1;
|
|
||||||
}
|
|
||||||
// ts,完整写法
|
|
||||||
let add:(a:number,b:number)=>number = function(a:number,b:number):number{
|
|
||||||
return a.search(b) !== -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
可选参数|默认参数|剩余参数
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
|
||||||
// 可选参数
|
// 可选参数
|
||||||
// 使用?放到参数前面,但可选参数的位置不能位于必选参数前
|
// 使用?放到参数前面,但可选参数的位置不能位于必选参数前
|
||||||
|
@ -246,7 +275,6 @@ console.log(getName('hello '));
|
||||||
|
|
||||||
}
|
}
|
||||||
fn('','',1,2,3,4,5)
|
fn('','',1,2,3,4,5)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
函数重载
|
函数重载
|
||||||
|
@ -255,7 +283,7 @@ fn('','',1,2,3,4,5)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### 类型断言
|
||||||
|
|
||||||
类型断言,可以手动指定一种类型。
|
类型断言,可以手动指定一种类型。
|
||||||
|
|
||||||
|
@ -309,7 +337,12 @@ function abc(x:any,y:any):any{
|
||||||
let a = abc(1,2) as number;//a-->数值类型
|
let a = abc(1,2) as number;//a-->数值类型
|
||||||
```
|
```
|
||||||
|
|
||||||
|
类型断言的特性:
|
||||||
|
|
||||||
|
- 联合类型可以被断言为其中一个类型
|
||||||
|
- 父类可以被断言为子类
|
||||||
|
- 任何类型都可以被断言为any
|
||||||
|
- any可以被断言为任何类型
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -161,7 +161,22 @@ Vue3中生命周期API(选项式 VS 组合式)
|
||||||
<script setup>
|
<script setup>
|
||||||
// 通过 deineProps “编译器宏函数” 接收父组件传递的数据
|
// 通过 deineProps “编译器宏函数” 接收父组件传递的数据
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
message:String
|
message:{
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
info:String
|
||||||
|
})
|
||||||
|
// ts泛型的写法
|
||||||
|
const props = defineProps<{
|
||||||
|
title:string
|
||||||
|
}>()
|
||||||
|
// ts泛型带上默认值
|
||||||
|
withDefaults(defineProps<{
|
||||||
|
message:string,
|
||||||
|
arr: string[]
|
||||||
|
}>(), {
|
||||||
|
arr:() => ['A', 'B']
|
||||||
})
|
})
|
||||||
console.log('父组件传入的值:',props.message)
|
console.log('父组件传入的值:',props.message)
|
||||||
</script>
|
</script>
|
||||||
|
@ -172,6 +187,26 @@ Vue3中生命周期API(选项式 VS 组合式)
|
||||||
|
|
||||||
> 如果父组件传入的是响应式数据,则数据变化,子组件接受的数据也会变化
|
> 如果父组件传入的是响应式数据,则数据变化,子组件接受的数据也会变化
|
||||||
|
|
||||||
|
扩展:利用ts的泛型限定传参类型,**message参数为必传,arr为可选**。
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script setup>
|
||||||
|
// 通过 deineProps “编译器宏函数” 接收父组件传递的数据
|
||||||
|
const props = defineProps<{
|
||||||
|
message:String,
|
||||||
|
arr?:string[]
|
||||||
|
}>()
|
||||||
|
console.log('父组件传入的值:',props.message)
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
{{message}}
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**子传父**
|
**子传父**
|
||||||
|
|
||||||
基本操作:
|
基本操作:
|
||||||
|
|