1. 개요

이 기사에서는 다용도 Java Security 프레임 워크 인 Apache Shiro를 살펴 보겠습니다 .

프레임 워크는 인증, 권한 부여, 암호화 및 세션 관리를 제공하므로 고도로 사용자 정의 가능하고 모듈 식입니다.

2. 의존성

Apache Shiro에는 많은 모듈이 있습니다. 그러나이 사용방법(예제)에서는 shiro-core 아티팩트 만 사용합니다.

pom.xml에 추가해 보겠습니다 .

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

Apache Shiro 모듈의 최신 버전은 Maven Central 에서 찾을 수 있습니다 .

3. Security 관리자 구성

Security 관리자는 아파치 시로의 프레임 워크의 중심 작품이다. 응용 프로그램은 일반적으로 단일 인스턴스를 실행합니다.

이 사용방법(예제)에서는 데스크톱 환경의 프레임 워크를 살펴 봅니다. 프레임 워크를 구성하려면 다음 내용으로 리소스 폴더에 shiro.ini 파일 을 만들어야합니다 .

[users]
user = password, admin
user2 = password2, editor
user3 = password3, author

[roles]
admin = *
editor = articles:*
author = articles:compose,articles:save

shiro.ini 구성 파일 [users] 섹션은 SecurityManager 가 인식하는 사용자 자격 증명을 정의합니다 . 형식은 다음과 같습니다. p rincipal (username) = password, role1, role2,…, role .

역할 및 관련 권한은 [roles] 섹션 에서 선언됩니다 . 관리자의 역할은 응용 프로그램의 모든 부분에 대한 권한 및 액세스 권한이 부여됩니다. 이는 와일드 카드 (*) 기호로 표시됩니다.

편집기 역할과 관련된 모든 권한이 기사 그동안 저자의 역할은 할 수 구성저장 기사를.

Security 관리자는 구성하는 데 사용됩니다 SecurityUtils의 클래스를. 로부터 SecurityUtils 우리가 시스템과 상호 작용하는 현재 사용자를 획득하고 인증 및 권한 부여 동작을 수행 할 수있다.

IniRealm사용하여 shiro.ini 파일 에서 사용자 및 역할 정의를로드 한 다음이를 사용하여 DefaultSecurityManager 개체 를 구성 해 보겠습니다 .

IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
SecurityManager securityManager = new DefaultSecurityManager(iniRealm);

SecurityUtils.setSecurityManager(securityManager);
Subject currentUser = SecurityUtils.getSubject();

이제 shiro.ini 파일에 정의 된 사용자 자격 증명 및 역할을 인식 하는 SecurityManager 가 있으므로 사용자 인증 및 권한 부여를 진행하겠습니다.

4. 인증

Apache Shiro의 용어에서 주제 는 시스템과 상호 작용하는 모든 엔티티입니다. 사람, 스크립트 또는 REST 클라이언트 일 수 있습니다.

SecurityUtils.getSubject ()를 호출 하면 현재 Subject 인스턴스 , 즉 currentUser반환 됩니다.

이제 우리는이 것을 currentUser의 개체, 우리는 제공된 자격 증명 인증을 수행 할 수 있습니다 :

if (!currentUser.isAuthenticated()) {               
  UsernamePasswordToken token                       
    = new UsernamePasswordToken("user", "password");
  token.setRememberMe(true);                        
  try {                                             
      currentUser.login(token);                       
  } catch (UnknownAccountException uae) {           
      log.error("Username Not Found!", uae);        
  } catch (IncorrectCredentialsException ice) {     
      log.error("Invalid Credentials!", ice);       
  } catch (LockedAccountException lae) {            
      log.error("Your Account is Locked!", lae);    
  } catch (AuthenticationException ae) {            
      log.error("Unexpected Error!", ae);           
  }                                                 
}

먼저 현재 사용자가 아직 인증되지 않았는지 확인합니다. 그런 다음 사용자의 주체 (사용자 이름) 및 자격 증명 (암호) 을 사용하여 인증 토큰을 만듭니다 .

다음으로 토큰으로 로그인을 시도합니다. 제공된 자격 증명이 올 바르면 모든 것이 잘 될 것입니다.

경우에 따라 다른 예외가 있습니다. 응용 프로그램 요구 사항에 더 적합한 사용자 지정 예외를 throw 할 수도 있습니다. AccountException 클래스 를 서브 클래 싱하여 수행 할 수 있습니다 .

5. 승인

인증은 시스템의 특정 리소스에 대한 액세스를 제어하는 ​​동안 사용자의 신원을 확인하려고합니다.

shiro.ini 파일 에서 생성 한 각 사용자에게 하나 이상의 역할을 할당 합니다. 또한 역할 섹션에서 각 역할에 대해 서로 다른 권한 또는 액세스 수준을 정의합니다.

이제 애플리케이션에서이를 사용하여 사용자 액세스 제어를 적용하는 방법을 살펴 보겠습니다.

에서 shiro.ini 파일, 우리는 시스템의 모든 부분에 대한 관리자 전체 액세스 할 수 있습니다.

편집자는 기사관련된 모든 리소스 / 작업에 대한 전체 액세스 권한을 가지며 작성자는 기사 작성 및 저장 만 가능합니다.

역할에 따라 현재 사용자를 환영합시다.

if (currentUser.hasRole("admin")) {       
    log.info("Welcome Admin");              
} else if(currentUser.hasRole("editor")) {
    log.info("Welcome, Editor!");           
} else if(currentUser.hasRole("author")) {
    log.info("Welcome, Author");            
} else {                                  
    log.info("Welcome, Guest");             
}

이제 현재 사용자가 시스템에서 수행 할 수있는 작업을 살펴 ​​보겠습니다.

if(currentUser.isPermitted("articles:compose")) {            
    log.info("You can compose an article");                    
} else {                                                     
    log.info("You are not permitted to compose an article!");
}                                                            
                                                             
if(currentUser.isPermitted("articles:save")) {               
    log.info("You can save articles");                         
} else {                                                     
    log.info("You can not save articles");                   
}                                                            
                                                             
if(currentUser.isPermitted("articles:publish")) {            
    log.info("You can publish articles");                      
} else {                                                     
    log.info("You can not publish articles");                
}

6. 영역 구성

실제 애플리케이션에서는 shiro.ini 파일이 아닌 데이터베이스에서 사용자 자격 증명을 가져 오는 방법이 필요 합니다. 이것은 Realm의 개념이 작용하는 곳입니다.

Apache Shiro의 용어에서 Realm 은 인증 및 권한 부여에 필요한 사용자 자격 증명 저장소를 가리키는 DAO입니다.

Realm 을 생성하려면 Realm 인터페이스 만 구현하면 됩니다. 지루할 수 있습니다. 그러나 프레임 워크에는 하위 클래스를 만들 수있는 기본 구현이 함께 제공됩니다. 이러한 구현 중 하나는 JdbcRealm 입니다.

JdbcRealm 클래스 를 확장 하고 doGetAuthenticationInfo () , doGetAuthorizationInfo () , getRoleNamesForUser ()getPermissions () 메서드를 재정의 하는 사용자 지정 영역 구현을 만듭니다 .

JdbcRealm 클래스 를 서브 클래 싱하여 영역을 생성 해 보겠습니다 .

public class MyCustomRealm extends JdbcRealm {
    //...
}

단순성을 위해 java.util.Map 을 사용하여 데이터베이스를 시뮬레이션합니다.

private Map<String, String> credentials = new HashMap<>();
private Map<String, Set<String>> roles = new HashMap<>();
private Map<String, Set<String>> perm = new HashMap<>();

{
    credentials.put("user", "password");
    credentials.put("user2", "password2");
    credentials.put("user3", "password3");
                                          
    roles.put("user", new HashSet<>(Arrays.asList("admin")));
    roles.put("user2", new HashSet<>(Arrays.asList("editor")));
    roles.put("user3", new HashSet<>(Arrays.asList("author")));
                                                             
    perm.put("admin", new HashSet<>(Arrays.asList("*")));
    perm.put("editor", new HashSet<>(Arrays.asList("articles:*")));
    perm.put("author", 
      new HashSet<>(Arrays.asList("articles:compose", 
      "articles:save")));
}

계속해서 doGetAuthenticationInfo ()를 재정의하겠습니다 .

protected AuthenticationInfo 
  doGetAuthenticationInfo(AuthenticationToken token)
  throws AuthenticationException {
                                                                 
    UsernamePasswordToken uToken = (UsernamePasswordToken) token;
                                                                
    if(uToken.getUsername() == null
      || uToken.getUsername().isEmpty()
      || !credentials.containsKey(uToken.getUsername())) {
          throw new UnknownAccountException("username not found!");
    }
                                        
    return new SimpleAuthenticationInfo(
      uToken.getUsername(), 
      credentials.get(uToken.getUsername()), 
      getName()); 
}

먼저 제공된 AuthenticationTokenUsernamePasswordToken에 캐스팅합니다 . 로부터 uToken , 우리는 (사용자 이름 추출 uToken.getUsername ()를 ) 및 데이터베이스에서 사용자 자격 증명 (비밀번호)를 얻기 위해 그것을 사용할 수 있습니다.

레코드가 없으면 -UnknownAccountException 을 던지고 , 그렇지 않으면 자격 증명과 사용자 이름을 사용 하여 메서드에서 반환 된 SimpleAuthenticatioInfo 개체 를 생성합니다 .

사용자 자격 증명이 솔트로 해시 된 경우 관련된 솔트와 함께 SimpleAuthenticationInfo 를 반환해야합니다 .

return new SimpleAuthenticationInfo(
  uToken.getUsername(), 
  credentials.get(uToken.getUsername()), 
  ByteSource.Util.bytes("salt"), 
  getName()
);

또한 doGetAuthorizationInfo () , getRoleNamesForUser ()getPermissions () 를 재정의해야합니다 .

마지막으로 사용자 정의 영역을 securityManager에 연결해 보겠습니다 . 우리가해야 할 일은 IniRealm을 사용자 정의 영역으로 바꾸고 DefaultSecurityManager 의 생성자에 전달하는 것입니다 .

Realm realm = new MyCustomRealm();
SecurityManager securityManager = new DefaultSecurityManager(realm);

코드의 다른 모든 부분은 이전과 동일합니다. 사용자 정의 영역으로 securityManager 를 올바르게 구성하는 데 필요한 모든 것 입니다.

이제 문제는 프레임 워크가 자격 증명과 어떻게 일치합니까?

기본적으로 JdbcRealm는 용도 SimpleCredentialsMatcher 의의 자격 증명을 비교하여 어떤지 단지 확인 인증 토큰AuthenticationInfo을 .

암호를 해시하는 경우 프레임 워크에 대신 HashedCredentialsMatcher 를 사용하도록 알려야 합니다. 해시 된 암호가있는 영역에 대한 INI 구성은 여기 에서 찾을 수 있습니다 .

7. 로그 아웃

이제 사용자를 인증 했으므로 로그 아웃을 구현할 차례입니다. 이는 사용자 세션을 무효화하고 사용자를 로그 아웃시키는 단일 메서드를 호출하여 간단히 수행됩니다.

currentUser.logout();

8. 세션 관리

프레임 워크는 자연스럽게 세션 관리 시스템과 함께 제공됩니다. 웹 환경에서 사용되는 경우 기본값은 HttpSession 구현입니다.

독립 실행 형 애플리케이션의 경우 엔터프라이즈 세션 관리 시스템을 사용합니다. 이점은 데스크톱 환경에서도 일반적인 웹 환경 에서처럼 세션 개체를 사용할 수 있다는 것입니다.

간단한 예제를 살펴보고 현재 사용자의 세션과 상호 작용 해 보겠습니다.

Session session = currentUser.getSession();                
session.setAttribute("key", "value");                      
String value = (String) session.getAttribute("key");       
if (value.equals("value")) {                               
    log.info("Retrieved the correct value! [" + value + "]");
}

9. Spring을 사용한 웹 애플리케이션을위한 Shiro

지금까지 Apache Shiro의 기본 구조에 대해 간략히 설명했으며이를 데스크탑 환경에서 구현했습니다. 프레임 워크를 Spring Boot 애플리케이션에 통합하여 진행하겠습니다.

여기서 주요 초점은 Spring 애플리케이션이 아니라 Shiro입니다. 간단한 예제 앱을 구동하는 데만 사용할 것입니다.

9.1. 의존성

먼저 pom.xml에 Spring Boot 부모 의존성을 추가해야합니다 .

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.0</version>
</parent>

다음으로 동일한 pom.xml 파일에 다음 의존성을 추가해야 합니다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-web-starter</artifactId>
    <version>${apache-shiro-core-version}</version>
</dependency>

9.2. 구성

pom.xml에 shiro-spring-boot-web-starter 의존성을 추가하면 기본적으로 SecurityManager 와 같은 Apache Shiro 애플리케이션의 일부 기능이 구성 됩니다.

그러나 여전히 Realm 및 Shiro Security 필터 를 구성해야 합니다. 위에서 정의한 것과 동일한 사용자 지정 영역을 사용합니다.

따라서 Spring Boot 애플리케이션이 실행되는 메인 클래스에 다음 Bean 정의를 추가해 보겠습니다 .

@Bean
public Realm realm() {
    return new MyCustomRealm();
}
    
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
    DefaultShiroFilterChainDefinition filter
      = new DefaultShiroFilterChainDefinition();

    filter.addPathDefinition("/secure", "authc");
    filter.addPathDefinition("/**", "anon");

    return filter;
}

에서 ShiroFilterChainDefinition 우리인가 authc 에 필터 / Security 경로 및인가 아논의 앤트 패턴을 이용하여 다른 경로에 필터.

authcanon 필터는 모두 웹 애플리케이션에 대해 기본적으로 제공됩니다. 다른 기본 필터는 여기 에서 찾을 수 있습니다 .

Realm 빈을 정의하지 않은 경우 ShiroAutoConfiguration 은 기본적 으로 src / main / resources 또는 src / main / resources / META-INF 에서 shiro.ini 파일 을 찾을 것으로 예상 하는 IniRealm 구현을 제공합니다 .

ShiroFilterChainDefinition 빈을 정의하지 않으면 프레임 워크는 모든 경로를 보호하고 로그인 URL을 login.jsp 로 설정합니다 .

application.properties에 다음 항목을 추가하여이 기본 로그인 URL 및 기타 기본값을 변경할 수 있습니다 .

shiro.loginUrl = /login
shiro.successUrl = /secure
shiro.unauthorizedUrl = /login

이제 authc 필터가 / secure 에 적용되었으므로 해당 경로에 대한 모든 요청에는 양식 인증이 필요합니다.

9.3. 인증 및 승인

/ index , / login, / logout/ secure 경로 매핑을 사용하여 ShiroSpringController생성 해 보겠습니다 .

로그인 () 방법은 상술 한 바와 같이 우리가 실제 사용자 인증을 구현하는 곳이다. 인증이 성공하면 사용자는 Security 페이지로 리디렉션됩니다.

Subject subject = SecurityUtils.getSubject();

if(!subject.isAuthenticated()) {
    UsernamePasswordToken token = new UsernamePasswordToken(
      cred.getUsername(), cred.getPassword(), cred.isRememberMe());
    try {
        subject.login(token);
    } catch (AuthenticationException ae) {
        ae.printStackTrace();
        attr.addFlashAttribute("error", "Invalid Credentials");
        return "redirect:/login";
    }
}

return "redirect:/secure";

이제 secure () 구현에서 currentUserSecurityUtils.getSubject ()를 호출하여 얻었습니다 . 사용자의 역할과 권한은 Security 페이지와 사용자의 주체로 전달됩니다.

Subject currentUser = SecurityUtils.getSubject();
String role = "", permission = "";

if(currentUser.hasRole("admin")) {
    role = role  + "You are an Admin";
} else if(currentUser.hasRole("editor")) {
    role = role + "You are an Editor";
} else if(currentUser.hasRole("author")) {
    role = role + "You are an Author";
}

if(currentUser.isPermitted("articles:compose")) {
    permission = permission + "You can compose an article, ";
} else {
    permission = permission + "You are not permitted to compose an article!, ";
}

if(currentUser.isPermitted("articles:save")) {
    permission = permission + "You can save articles, ";
} else {
    permission = permission + "\nYou can not save articles, ";
}

if(currentUser.isPermitted("articles:publish")) {
    permission = permission  + "\nYou can publish articles";
} else {
    permission = permission + "\nYou can not publish articles";
}

modelMap.addAttribute("username", currentUser.getPrincipal());
modelMap.addAttribute("permission", permission);
modelMap.addAttribute("role", role);

return "secure";

그리고 우리는 끝났습니다. 이것이 Apache Shiro를 Spring Boot 애플리케이션에 통합하는 방법입니다.

또한 프레임 워크는 애플리케이션을 보호하기 위해 필터 체인 정의와 함께 사용할 수있는 추가 어노테이션제공 합니다.

10. JEE 통합

Apache Shiro를 JEE 애플리케이션에 통합하는 것은 web.xml 파일 을 구성하기 만하면 됩니다. 평소와 같이 구성에서는 shiro.ini 가 클래스 경로에있을 것으로 예상 합니다. 자세한 구성 예는 여기에서 확인할 수 있습니다 . 또한 JSP 태그는 여기 에서 찾을 수 있습니다 .

11. 결론

이 사용방법(예제)에서는 Apache Shiro의 인증 및 권한 부여 메커니즘을 살펴 보았습니다. 또한 사용자 정의 영역을 정의하고이를 SecurityManager에 연결하는 방법에 중점을 두었습니다 .

항상 그렇듯이 전체 소스 코드는 GitHub에서 사용할 수 있습니다 .