diff --git a/.github/semantic.yml b/.github/semantic.yml new file mode 100644 index 0000000..b172b6a --- /dev/null +++ b/.github/semantic.yml @@ -0,0 +1,2 @@ +# Always validate the PR title AND all the commits +titleAndCommits: false diff --git a/.github/workflows/gradle-ci.yml b/.github/workflows/gradle-ci.yml new file mode 100644 index 0000000..bfba7ab --- /dev/null +++ b/.github/workflows/gradle-ci.yml @@ -0,0 +1,85 @@ +name: build + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + services: + mysql: + image: mysql + env: + MYSQL_ROOT_PASSWORD: casbin_test + MYSQL_DATABASE: casbin + MYSQL_USER: casbin_test + MYSQL_PASSWORD: TEST_casbin + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + postgres: + image: postgres + env: + POSTGRES_DB: casbin + POSTGRES_USER: casbin_test + POSTGRES_PASSWORD: TEST_casbin + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + sqlserver: + image: mcr.microsoft.com/mssql/server:2019-latest + env: + SA_PASSWORD: 9G3iqmzQDw9zCXII + ACCEPT_EULA: Y + ports: + - 1433:1433 + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: '0' + + - name: Install mssql-tools + run: | + curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - + curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list | sudo tee /etc/apt/sources.list.d/msprod.list + sudo apt-get update + sudo apt-get install mssql-tools unixodbc-dev + echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bash_profile + + - name: Create database for sqlserver + run: sqlcmd -S 127.0.0.1,1433 -U sa -P '9G3iqmzQDw9zCXII' -Q "CREATE DATABASE casbin" + + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + server-id: ossrh + server-username: OSSRH_JIRA_USERNAME + server-password: OSSRH_JIRA_PASSWORD + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: GPG_PASSPHRASE + + - name: Build with Maven + run: mvn clean test cobertura:cobertura + + - name: Codecov + uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: 18 + + - name: Sematic Release + run: | + npm install -g @conveyal/maven-semantic-release semantic-release + semantic-release --prepare @conveyal/maven-semantic-release --publish @semantic-release/github,@conveyal/maven-semantic-release --verify-conditions @semantic-release/github,@conveyal/maven-semantic-release --verify-release @conveyal/maven-semantic-release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GPG_KEY_NAME: ${{ secrets.GPG_KEY_NAME }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + OSSRH_JIRA_USERNAME: ${{ secrets.OSSRH_JIRA_USERNAME }} + OSSRH_JIRA_PASSWORD: ${{ secrets.OSSRH_JIRA_PASSWORD }} diff --git a/.github/workflows/maven-ci.yml b/.github/workflows/maven-ci.yml new file mode 100644 index 0000000..5bca5bd --- /dev/null +++ b/.github/workflows/maven-ci.yml @@ -0,0 +1,73 @@ +name: build + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + services: + mysql: + image: mysql + env: + MYSQL_ROOT_PASSWORD: casbin_test + MYSQL_DATABASE: casbin + MYSQL_USER: casbin_test + MYSQL_PASSWORD: TEST_casbin + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + postgres: + image: postgres + env: + POSTGRES_DB: casbin + POSTGRES_USER: casbin_test + POSTGRES_PASSWORD: TEST_casbin + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + sqlserver: + image: mcr.microsoft.com/mssql/server:2019-latest + env: + SA_PASSWORD: 9G3iqmzQDw9zCXII + ACCEPT_EULA: Y + ports: + - 1433:1433 + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: '0' + + - name: Install mssql-tools + run: | + curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - + curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list | sudo tee /etc/apt/sources.list.d/msprod.list + sudo apt-get update + sudo apt-get install mssql-tools unixodbc-dev + echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bash_profile + - name: Create database for sqlserver + run: sqlcmd -S 127.0.0.1,1433 -U sa -P '9G3iqmzQDw9zCXII' -Q "CREATE DATABASE casbin" + + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + server-id: ossrh + server-username: OSSRH_JIRA_USERNAME + server-password: OSSRH_JIRA_PASSWORD + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: GPG_PASSPHRASE + + - name: Build with Maven + run: mvn clean test cobertura:cobertura + + - name: Codecov + uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: 20.8.1 \ No newline at end of file diff --git a/.gitignore b/.gitignore index a1c2a23..489ed35 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* + +.idea/ +*.iml diff --git a/README.md b/README.md index a0125fc..5172ed5 100644 --- a/README.md +++ b/README.md @@ -1 +1,19 @@ -# casbin-spring-security-starter \ No newline at end of file +# using casbin in SpringSecurity + +## inform + To skip the tedious and complicated login process of Spring Security, + we're using its built-in login mode, which allows us to focus on the model authentication module. + +## DataBase + For the database, we're using mysql. + Remember to create a casbin database if you don't have one, and configure it in the application.yml file. + +## Simple Example + check file examples.rbac.policy.csv you can find out we are using rbac model + I registered with the identity of Alice who can read data1 +![img_1.png](img_1.png) + + Than test the result +![img_2.png](img_2.png) + +![img_3.png](img_3.png) \ No newline at end of file diff --git a/examples/rbac_model.conf b/examples/rbac_model.conf new file mode 100644 index 0000000..71159e3 --- /dev/null +++ b/examples/rbac_model.conf @@ -0,0 +1,14 @@ +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act + +[role_definition] +g = _, _ + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act \ No newline at end of file diff --git a/examples/rbac_policy.csv b/examples/rbac_policy.csv new file mode 100644 index 0000000..f93d6df --- /dev/null +++ b/examples/rbac_policy.csv @@ -0,0 +1,5 @@ +p, alice, data1, read +p, bob, data2, write +p, data2_admin, data2, read +p, data2_admin, data2, write +g, alice, data2_admin \ No newline at end of file diff --git a/examples/rbac_with_domains_model.conf b/examples/rbac_with_domains_model.conf new file mode 100644 index 0000000..57c3721 --- /dev/null +++ b/examples/rbac_with_domains_model.conf @@ -0,0 +1,14 @@ +[request_definition] +r = sub, dom, obj, act + +[policy_definition] +p = sub, dom, obj, act + +[role_definition] +g = _, _, _ + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act \ No newline at end of file diff --git a/examples/rbac_with_domains_policy.csv b/examples/rbac_with_domains_policy.csv new file mode 100644 index 0000000..e69de29 diff --git a/img.png b/img.png new file mode 100644 index 0000000..e69de29 diff --git a/img_1.png b/img_1.png new file mode 100644 index 0000000..448d6b5 Binary files /dev/null and b/img_1.png differ diff --git a/img_2.png b/img_2.png new file mode 100644 index 0000000..e69de29 diff --git a/img_3.png b/img_3.png new file mode 100644 index 0000000..e69de29 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..402ff95 --- /dev/null +++ b/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + groupId + casbin-spring-security-starter + 1.0-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.2.6.RELEASE + + + + + mysql + mysql-connector-java + + + org.casbin + jdbc-adapter + 2.5.0 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-test + + + + + \ No newline at end of file diff --git a/src/main/java/org/casbin/Application.java b/src/main/java/org/casbin/Application.java new file mode 100644 index 0000000..ec4219c --- /dev/null +++ b/src/main/java/org/casbin/Application.java @@ -0,0 +1,26 @@ +// Copyright 2024 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.casbin; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication + +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} diff --git a/src/main/java/org/casbin/api/CommonResult.java b/src/main/java/org/casbin/api/CommonResult.java new file mode 100644 index 0000000..a6406ee --- /dev/null +++ b/src/main/java/org/casbin/api/CommonResult.java @@ -0,0 +1,96 @@ +// Copyright 2024 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package org.casbin.api; + +/** + * return Result + */ +public class CommonResult { + private long code; + private String message; + private T data; + + protected CommonResult() { + } + + protected CommonResult(long code, String message, T data) { + this.code = code; + this.message = message; + this.data = data; + } + + public static CommonResult success(T data) { + return new CommonResult(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data); + } + + public static CommonResult success(T data, String message) { + return new CommonResult(ResultCode.SUCCESS.getCode(), message, data); + } + + public static CommonResult failed(IErrorCode errorCode) { + return new CommonResult(errorCode.getCode(), errorCode.getMessage(), null); + } + + public static CommonResult failed(String message) { + return new CommonResult(ResultCode.FAILED.getCode(), message, null); + } + + + public static CommonResult failed() { + return failed(ResultCode.FAILED); + } + + public static CommonResult validateFailed() { + return failed(ResultCode.VALIDATE_FAILED); + } + + + public static CommonResult validateFailed(String message) { + return new CommonResult(ResultCode.VALIDATE_FAILED.getCode(), message, null); + } + + + public static CommonResult unauthorized(T data) { + return new CommonResult(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data); + } + + + public static CommonResult forbidden(T data) { + return new CommonResult(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data); + } + + public long getCode() { + return code; + } + + public void setCode(long code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } +} diff --git a/src/main/java/org/casbin/api/IErrorCode.java b/src/main/java/org/casbin/api/IErrorCode.java new file mode 100644 index 0000000..581fe27 --- /dev/null +++ b/src/main/java/org/casbin/api/IErrorCode.java @@ -0,0 +1,20 @@ +// Copyright 2024 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package org.casbin.api; + +public interface IErrorCode { + long getCode(); + + String getMessage(); +} diff --git a/src/main/java/org/casbin/api/ResultCode.java b/src/main/java/org/casbin/api/ResultCode.java new file mode 100644 index 0000000..24e779c --- /dev/null +++ b/src/main/java/org/casbin/api/ResultCode.java @@ -0,0 +1,40 @@ +// Copyright 2024 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package org.casbin.api; + +public enum ResultCode implements IErrorCode { + SUCCESS(200, "success"), + FAILED(500, "fail"), + VALIDATE_FAILED(404, "Parameter Validation Failed"), + UNAUTHORIZED(401, "token expire"), + FORBIDDEN(403, "without authority"); + private long code; + private String message; + + ResultCode(long code, String message) { + this.code = code; + this.message = message; + } + + @Override + public long getCode() { + return code; + } + + @Override + public String getMessage() { + return message; + } +} + diff --git a/src/main/java/org/casbin/config/UserSecurityConfig.java b/src/main/java/org/casbin/config/UserSecurityConfig.java new file mode 100644 index 0000000..97017ed --- /dev/null +++ b/src/main/java/org/casbin/config/UserSecurityConfig.java @@ -0,0 +1,31 @@ +// Copyright 2024 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package org.casbin.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) +public class UserSecurityConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + http.authorizeRequests(). + antMatchers("/**").permitAll(); + } +} diff --git a/src/main/java/org/casbin/controller/AdapterController.java b/src/main/java/org/casbin/controller/AdapterController.java new file mode 100644 index 0000000..857e76e --- /dev/null +++ b/src/main/java/org/casbin/controller/AdapterController.java @@ -0,0 +1,85 @@ +// Copyright 2024 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.casbin.controller; + +import org.casbin.adapter.JDBCAdapter; +import org.casbin.api.CommonResult; +import org.casbin.jcasbin.main.Enforcer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpSession; +import java.util.ArrayList; + + +@RestController +@RequestMapping("/casbin") +public class AdapterController { + @Value(value = "${mysql.url}") + private String url; + @Value(value = "${mysql.username}") + private String username; + @Value(value = "${mysql.password}") + private String password; + @Value(value = "${mysql.driver}") + private String driver; + + @RequestMapping("/InitDataAndLogin") + public CommonResult InitDataAndLogin(Authentication authentication, HttpSession session) throws Exception { + + init(); + SecurityContext context = SecurityContextHolder.getContext(); + + SimpleGrantedAuthority authority = new SimpleGrantedAuthority("readData1"); + ArrayList arrayList = new ArrayList<>(); + arrayList.add(authority); + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("alice", "pass", arrayList); + + context.setAuthentication(token); + return CommonResult.success("login success, init data"); + } + + @RequestMapping("/canReadData1") + @PreAuthorize("hasAuthority('readData1')") + public CommonResult canReadData1() { + return CommonResult.success("you can read data1"); + } + + @RequestMapping("/canNotReadData2") + @PreAuthorize("hasAuthority('readData2')") + public CommonResult canNotReadData() { + return CommonResult.success("you can read data2"); + } + + /** + * use init once to load model data into mysql database + * then you can dismiss it + * + * @throws Exception + */ + public void init() throws Exception { + Enforcer e = new Enforcer("examples/rbac_model.conf", "examples/rbac_policy.csv"); + JDBCAdapter adapter = new JDBCAdapter(driver, url, username, password); + adapter.savePolicy(e.getModel()); + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..65dc640 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,12 @@ +server: + port: 8090 +spring: + security: + user: + name: user + password: pass +mysql: + url: jdbc:mysql://localhost:3306/casbin?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=PRC&characterEncoding=UTF8 + username: root + password: 38941abc + driver: com.mysql.cj.jdbc.Driver \ No newline at end of file diff --git a/src/test/java/org/casbin/JdbcTest.java b/src/test/java/org/casbin/JdbcTest.java new file mode 100644 index 0000000..c275cd0 --- /dev/null +++ b/src/test/java/org/casbin/JdbcTest.java @@ -0,0 +1,56 @@ +// Copyright 2024 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.casbin; + +import org.casbin.adapter.JDBCAdapter; +import org.casbin.jcasbin.main.Enforcer; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit4.SpringRunner; + +@SpringBootTest +public class JdbcTest { + + private String url="jdbc:mysql://localhost:3306/casbin?useSSL=false&allowPublicKeyRetrieval=true"; + private String username="root"; + private String password="casbin_test"; + private String driver="com.mysql.jdbc.Driver"; + + @Test + public void TestJdbc() throws Exception { + //save policy to database + Enforcer e = new Enforcer("examples/rbac_model.conf", "examples/rbac_policy.csv"); + JDBCAdapter adapter = new JDBCAdapter(driver, url, username, password); + adapter.savePolicy(e.getModel()); + + //read policy to database + e.clearPolicy(); + adapter.loadPolicy(e.getModel()); + + Enforcer newOne=new Enforcer("examples/rbac_model.conf",e.getAdapter()); + System.out.println(newOne.getPolicy()); + + } + + @Test + public void TestModelAuth(){ + Enforcer e = new Enforcer("examples/rbac_model.conf", "examples/rbac_policy.csv"); + boolean enforce = e.enforce("alice", "data1", "read"); + System.out.println(enforce); + } +} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml new file mode 100644 index 0000000..5970557 --- /dev/null +++ b/src/test/resources/application.yml @@ -0,0 +1,7 @@ +server: + port: 8090 +spring: + security: + user: + name: user + password: pass