Tích hợp Apache Shiro vào ứng dụng Spring Boot

Để tích hợp Apache Shiro vào dự án Spring Boot, bạn cần thực hiện các bước cấu hình cơ bản sau:

1. Thêm phụ thuộc Maven

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.5.3</version>
    </dependency>

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-ehcache</artifactId>
        <version>1.5.3</version>
    </dependency>

    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.7.15</version>
    </dependency>
</dependencies>

2. Tạo lớp Realm tùy chỉnh

package com.example.security;

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.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;

public class AppRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = (String) principals.getPrimaryPrincipal();
        User currentUser = userService.findByUsername(username);

        SimpleAuthorizationInfo authInfo = new SimpleAuthorizationInfo();
        
        currentUser.getRoles().forEach(role -> {
            authInfo.addRole(role.getName());
            role.getPermissions().forEach(perm -> 
                authInfo.addStringPermission(perm.getCode())
            );
        });

        return authInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) 
            throws AuthenticationException {
        String username = token.getPrincipal().toString();
        
        if (StringUtils.isEmpty(username)) {
            return null;
        }

        User user = userService.findByUsername(username);
        if (user == null) {
            throw new UnknownAccountException("Tài khoản không tồn tại");
        }

        return new SimpleAuthenticationInfo(
            username, 
            user.getPassword(), 
            getName()
        );
    }
}

3. Cấu hình Shiro trong Spring

package com.example.config;

import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.InputStream;
import java.util.LinkedHashMap;

@Configuration
public class SecurityConfig {

    @Bean
    public EhCacheManager ehCacheManager() {
        EhCacheManager cacheManager = new EhCacheManager();
        cacheManager.setCacheManagerConfigFile("classpath:ehcache/shiro-ehcache.xml");
        return cacheManager;
    }

    @Bean
    public AppRealm appRealm(EhCacheManager cacheManager) {
        AppRealm realm = new AppRealm();
        realm.setCacheManager(cacheManager);
        return realm;
    }

    @Bean
    public SecurityManager securityManager(AppRealm realm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(realm);
        manager.setCacheManager(ehCacheManager());
        return manager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean factory = new ShiroFilterFactoryBean();
        factory.setSecurityManager(securityManager);

        LinkedHashMap<String, String> filterChain = new LinkedHashMap<>();
        filterChain.put("/login", "anon");
        filterChain.put("/logout", "logout");
        filterChain.put("/**", "authc");

        factory.setFilterChainDefinitionMap(filterChain);
        factory.setLoginUrl("/login");
        factory.setSuccessUrl("/dashboard");
        factory.setUnauthorizedUrl("/403");

        return factory;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor advisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

Nguyên lý hoạt động

Khi người dùng đăng nhập, Shiro sẽ gọi phương thức doGetAuthenticationInfo trong Realm để xác thực thông tin đăng nhập. Sau khi đăng nhập thành công, mỗi lần truy cập tài nguyên được bảo vệ (có annotation @RequiresPermissions), Shiro sẽ gọi doGetAuthorizationInfo để kiểm tra quyền hạn.

Lưu ý: Nên bật bộ nhớ đệm (cache) để tránh gọi lại phương thức kiểm tra quyền nhiều lần không cần thiết. Có thể thay thế EhCache bằng Redis bằng cách triển khai interface CacheManager và cấu hình trong SecurityManager.

Thẻ: shiro spring-boot Security authorization authentication

Đăng vào ngày 16 tháng 6 lúc 19:39