Skip to content

Commit

Permalink
Extended functions.
Browse files Browse the repository at this point in the history
  • Loading branch information
shy1st committed Feb 17, 2021
1 parent 43816f5 commit e3249ae
Show file tree
Hide file tree
Showing 14 changed files with 175 additions and 27 deletions.
5 changes: 5 additions & 0 deletions pom.xml
Expand Up @@ -247,6 +247,11 @@
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring-boot.version}</version>
</dependency>

<!-- for test -->
<dependency>
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/casbin/shiro/advisor/ShiroAdvisor.java
Expand Up @@ -15,8 +15,8 @@
package org.casbin.shiro.advisor;

import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.casbin.shiro.authorize.rbac.annotation.auth.EnforcerAuth;
import org.casbin.shiro.authorize.rbac.annotation.auth.interceptor.EnforcerAuthAopInterceptor;
import org.casbin.shiro.authorize.rbac.annotation.auth.*;
import org.casbin.shiro.authorize.rbac.annotation.auth.interceptor.*;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.reflect.Method;
Expand Down
Expand Up @@ -18,11 +18,15 @@
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.authz.aop.AuthorizingAnnotationHandler;
import org.apache.shiro.web.subject.support.WebDelegatingSubject;
import org.casbin.shiro.util.LogUtil;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import static org.casbin.shiro.factory.EnforcerFactory.getEnforcer;
import static org.casbin.shiro.factory.EnforcerFactory.getUserMethodName;


/**
Expand All @@ -33,7 +37,6 @@
*/
public class EnforcerAuthHandler extends AuthorizingAnnotationHandler {


/**
* Constructs an <code>AuthorizingAnnotationHandler</code> who processes annotations of the
* specified type. Immediately calls <code>super(annotationClass)</code>.
Expand Down Expand Up @@ -64,7 +67,25 @@ public void assertAuthorized(Annotation a) throws AuthorizationException {
HttpServletRequest request = (HttpServletRequest) subject.getServletRequest();
String path = request.getServletPath();
String method = request.getMethod();
boolean hasPermission = getEnforcer().enforce(subject.getPrincipal().toString(), path, method);
boolean hasPermission = false;
if (getUserMethodName() == null) {
hasPermission = getEnforcer().enforce(subject.getPrincipal().toString(), path, method);
} else {
// Using reflection to call the specified method
Object principal = subject.getPrincipal();
Class<?> principalClass = principal.getClass();
Object principalCast = principalClass.cast(principal);
String userName;
try {
Method principalMethod = principalClass.getMethod(getUserMethodName());
userName = (String) principalMethod.invoke(principalCast);
hasPermission = getEnforcer().enforce(userName, path, method);
} catch (NoSuchMethodException e) {
LogUtil.logPrintfError("Incorrect userNameMethodName");
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
if (!hasPermission) {
throw new UnauthorizedException("No permission to access this interface");
}
Expand Down
Expand Up @@ -17,12 +17,16 @@
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* shiro-casbin configuration properties.
*
* @author shy
* @since 2021/02/17
*/
@ConfigurationProperties(prefix = "shiro-casbin")
public class EnforcerConfigProperties {
private String modelPath;
private String policyPath;
private String userNameMethodName;

public String getModelPath() {
return modelPath;
Expand All @@ -39,4 +43,12 @@ public String getPolicyPath() {
public void setPolicyPath(String policyPath) {
this.policyPath = policyPath;
}

public String getUserNameMethodName() {
return userNameMethodName;
}

public void setUserNameMethodName(String userNameMethodName) {
this.userNameMethodName = userNameMethodName;
}
}
17 changes: 13 additions & 4 deletions src/main/java/org/casbin/shiro/factory/EnforcerFactory.java
Expand Up @@ -16,7 +16,8 @@

import org.casbin.adapter.JDBCAdapter;
import org.casbin.jcasbin.main.Enforcer;
import org.casbin.shiro.config.EnforcerConfigProperties;
import org.casbin.jcasbin.persist.file_adapter.FileAdapter;
import org.casbin.shiro.config.*;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
Expand All @@ -38,6 +39,7 @@ public class EnforcerFactory implements InitializingBean {

private static Enforcer enforcer;
private static JDBCAdapter jdbcAdapter;
private static String userMethodName;

@Autowired
private EnforcerConfigProperties properties;
Expand All @@ -51,10 +53,13 @@ public class EnforcerFactory implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
jdbcAdapter = new JDBCAdapter(dataSource);
String modelPath = getResourcePath(this.properties.getModelPath());
String policyPath = getResourcePath(this.properties.getPolicyPath());
enforcer = new Enforcer(modelPath, policyPath);
String modelPath = getResourcePath(properties.getModelPath());
String policyPath = getResourcePath(properties.getPolicyPath());
FileAdapter fileAdapter = new FileAdapter(policyPath);
enforcer = new Enforcer(modelPath, fileAdapter);
jdbcAdapter.savePolicy(enforcer.getModel());
jdbcAdapter.close();
userMethodName = properties.getUserNameMethodName();
}

public static Enforcer getEnforcer() {
Expand All @@ -65,6 +70,10 @@ public static JDBCAdapter getJdbcAdapter() {
return jdbcAdapter;
}

public static String getUserMethodName() {
return userMethodName;
}

/**
* Get the resource path which can used by enforcer.
*
Expand Down
14 changes: 13 additions & 1 deletion src/test/java/org/casbin/shiro/config/ShiroConfig.java
Expand Up @@ -20,19 +20,30 @@
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.casbin.shiro.advisor.ShiroAdvisor;
import org.casbin.shiro.realm.UserRealm;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class ShiroConfig {

/**
* IniRealm is for tests without user name method name
*/
@Bean
public IniRealm realm() {
return new IniRealm("classpath:shiro.ini");
}

/**
* UserRealm is for tests without user name method name
*/
@Bean
public UserRealm userRealm() {
return new UserRealm();
}

@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
Expand All @@ -44,6 +55,7 @@ public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager s
public DefaultWebSecurityManager securityManager() throws Exception {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm());
securityManager.setRealm(userRealm());
return securityManager;
}

Expand Down
Expand Up @@ -17,7 +17,7 @@
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.casbin.shiro.authorize.rbac.annotation.auth.EnforcerAuth;
import org.casbin.shiro.authorize.rbac.annotation.auth.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -40,13 +40,13 @@ public String login() {
}

@EnforcerAuth
@GetMapping("/data1")
@GetMapping("/data/data1")
public String function1() {
return "success";
}

@EnforcerAuth
@PostMapping("/data2")
@PostMapping("/data/data2")
public String function2() {
return "success";
}
Expand Down
20 changes: 20 additions & 0 deletions src/test/java/org/casbin/shiro/entity/User.java
@@ -0,0 +1,20 @@
package org.casbin.shiro.entity;

public class User {
private String username;

public User() {
}

public User(String username) {
this.username = username;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}
}
Expand Up @@ -26,7 +26,9 @@
import java.util.HashMap;
import java.util.Objects;


/**
* Test with user-name-method-name property.
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@RunWith(SpringRunner.class)
public class AuthAnnotationControllerTest {
Expand All @@ -42,22 +44,22 @@ public void testAuthAnnotationController() {
// alice login
login("/login?username=alice&password=123");
// test api data1
responseEntity = restTemplate.exchange("/data1", HttpMethod.GET, httpEntity, String.class);
responseEntity = restTemplate.exchange("/data/data1", HttpMethod.GET, httpEntity, String.class);
Assert.assertEquals(responseEntity.getStatusCode(), HttpStatus.OK);
// test api data2
responseEntity = restTemplate.exchange("/data2", HttpMethod.POST, httpEntity, String.class);
Assert.assertEquals(responseEntity.getStatusCode(), HttpStatus.INTERNAL_SERVER_ERROR);
responseEntity = restTemplate.exchange("/data/data2", HttpMethod.POST, httpEntity, String.class);
Assert.assertEquals(responseEntity.getStatusCode(), HttpStatus.OK);
// alice logout
logout();

// bob login
login("/login?username=bob&password=123");
// test api data1
responseEntity = restTemplate.exchange("/data1", HttpMethod.GET, httpEntity, String.class);
Assert.assertEquals(responseEntity.getStatusCode(), HttpStatus.INTERNAL_SERVER_ERROR);
// test api data2
responseEntity = restTemplate.exchange("/data2", HttpMethod.POST, httpEntity, String.class);
responseEntity = restTemplate.exchange("/data/data1", HttpMethod.GET, httpEntity, String.class);
Assert.assertEquals(responseEntity.getStatusCode(), HttpStatus.OK);
// test api data2
responseEntity = restTemplate.exchange("/data/data2", HttpMethod.POST, httpEntity, String.class);
Assert.assertEquals(responseEntity.getStatusCode(), HttpStatus.INTERNAL_SERVER_ERROR);
// bob logout
logout();
}
Expand All @@ -78,3 +80,4 @@ private void logout() {
Assert.assertEquals(result, "success");
}
}

63 changes: 63 additions & 0 deletions src/test/java/org/casbin/shiro/realm/UserRealm.java
@@ -0,0 +1,63 @@
// Copyright 2021 The casbin 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.shiro.realm;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.casbin.shiro.entity.*;

public class UserRealm extends AuthorizingRealm {
/**
* Retrieves the AuthorizationInfo for the given principals from the underlying data store. When returning
* an instance from this method, you might want to consider using an instance of
* {@link SimpleAuthorizationInfo SimpleAuthorizationInfo}, as it is suitable in most cases.
*
* @param principals the primary identifying principals of the AuthorizationInfo that should be retrieved.
* @return the AuthorizationInfo associated with this principals.
* @see SimpleAuthorizationInfo
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return new SimpleAuthorizationInfo();
}

/**
* Retrieves authentication data from an implementation-specific datasource (RDBMS, LDAP, etc) for the given
* authentication token.
* <p/>
* For most datasources, this means just 'pulling' authentication data for an associated subject/user and nothing
* more and letting Shiro do the rest. But in some systems, this method could actually perform EIS specific
* log-in logic in addition to just retrieving data - it is up to the Realm implementation.
* <p/>
* A {@code null} return value means that no account could be associated with the specified token.
*
* @param token the authentication token containing the user's principal and credentials.
* @return an {@link AuthenticationInfo} object containing account data resulting from the
* authentication ONLY if the lookup is successful (i.e. account exists and is valid, etc.)
* @throws AuthenticationException if there is an error acquiring data or performing
* realm-specific authentication logic for the specified <tt>token</tt>
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String username = upToken.getUsername();
String password = new String(upToken.getPassword());
User user = new User(username);
return new SimpleAuthenticationInfo(user, password, getName());
}
}
9 changes: 5 additions & 4 deletions src/test/resources/application.yml
@@ -1,10 +1,11 @@
shiro-casbin:
modelPath: classpath:casbin/rbac_model.conf
policyPath: classpath:casbin/rbac_policy.csv

spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/casbin?serverTimezone=GMT%2B8
username: casbin_test
password: TEST_casbin

shiro-casbin:
model-path: classpath:casbin/keymatch_model.conf
policy-path: classpath:casbin/keymatch_policy.csv
user-name-method-name: getUsername
Expand Up @@ -11,4 +11,4 @@ g = _, _
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)
4 changes: 4 additions & 0 deletions src/test/resources/casbin/keymatch_policy.csv
@@ -0,0 +1,4 @@
p, admin, /data/*, (GET|POST)
p, common, /data/data1, GET
g, alice, admin
g, bob, common
2 changes: 0 additions & 2 deletions src/test/resources/casbin/rbac_policy.csv

This file was deleted.

0 comments on commit e3249ae

Please sign in to comment.