SpringBoot統合史郎セキュリティフレームワーク

SpringBoot 単純化するために  Spring 、アプリケーションの作成を実行し、デバッグ、このような製品の誕生などの問題、の範囲に展開私たちはより良いではなく、外部のXML構成よりも、ビジネスそのものに集中できるように、自動組立の特性を、我々は単にの導入に関連した規範に従ってください簡単にWEBプロジェクトをビルドすることができます依存性

Apacheのオープンソース史郎は、認証、承認、暗号化、セッション管理のためのJavaセキュリティフレームワークを強力で使いやすいです。 比べる  Spring Security ために  Shiro、より軽量で、APIを理解するのは簡単です...

Shiro

Shiro 分け  セキュアな認証  と  のインタフェース承認  二つの部分、のためのコアコンポーネントの1の  Subject、、 SecurityManagerRealms公開部分は、  Shiro 我々だけで対応するコードを記述するために特定のルールに従う必要があり、私たちのために良いパッケージを持っています...

  • 被験体が その体を意味することは、ユーザーのブラウザからの要求であってもよく、またはそれは、被験体と相互作用する外部アプリケーションは、ユーザの現在の記録動作を実行するプログラムであってもよいように、ユーザは、すなわち、現在の操作の主な概念として理解されるであろう。件名は、現在のユーザーのセキュリティ動作を表し、セキュリティマネージャは、セキュリティ操作のすべてのユーザーを管理します。
  • セキュリティマネージャ すなわちセキュリティマネージャ、すべての件名のセキュリティ管理、及びその安全管理を通じて様々なサービス(認証、許可、など)を提供するために、
  • レルム  間のアプリケーションおよびデータセキュリティなどの行為  ブリッジ  または  コネクターユーザーが認証(ログイン)および承認(アクセス制御)検証を実行すると、史郎は、アプリケーション構成レルム内のユーザーとその権限の情報を探します。

本章の目的

使用  Spring Boot して  Shiro セキュアな認証および承認を実装する....

輸入依存度

依存  spring-boot-starter-web...

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <shiro.version>1.4.0</shiro.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- shiro 相关包 -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>${shiro.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>${shiro.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-ehcache</artifactId>
        <version>${shiro.version}</version>
    </dependency>
    <!-- End  -->
</dependencies>

構成プロパティ

キャッシュの設定

史郎が私たちを提供して  CacheManager キャッシュ管理に、それはユーザー権利のデータストレージのパフォーマンスを向上させることができ、キャッシュになります。サポート  EhCacheRedis ここで使用するための簡略化のため、および他の従来のキャッシュ、  EhCache 、およびでresources ディレクトリの作成  ehcache-shiro.xml 、ファイルを

<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="shiroCache">
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
    />
</ehcache>

Entityクラス

作成  User.java ラベルされたデータベース・ユーザーを

package com.battcn.entity;

/**
 * @author Levin
 * @since 2018/6/28 0028
 */
public class User {
    /** 自增ID */
    private Long id;
    /** 账号 */
    private String username;
    /** 密码 */
    private String password;
    /** 角色名:Shiro 支持多个角色,而且接收参数也是 Set<String> 集合,但这里为了简单起见定义成 String 类型了 */
    private String roleName;
    /** 是否禁用 */
    private boolean locked;
    // 省略 GET SET 构造函数...
}

偽造データ

サポートは  rolespermissionsそのようなユーザーが一定の役割を持たせることができますインターフェイスとして、また、特定のを持っていることがあり  permission ...

package com.battcn.config;

import com.battcn.entity.User;

import java.util.*;

/**
 * 主要不想连接数据库..
 *
 * @author Levin
 * @since 2018/6/28 0028
 */
public class DBCache {

    /**
     * K 用户名
     * V 用户信息
     */
    public static final Map<String, User> USERS_CACHE = new HashMap<>();
    /**
     * K 角色ID
     * V 权限编码
     */
    public static final Map<String, Collection<String>> PERMISSIONS_CACHE = new HashMap<>();

    static {
        // TODO 假设这是数据库记录
        USERS_CACHE.put("u1", new User(1L, "u1", "p1", "admin", true));
        USERS_CACHE.put("u2", new User(2L, "u2", "p2", "admin", false));
        USERS_CACHE.put("u3", new User(3L, "u3", "p3", "test", true));

        PERMISSIONS_CACHE.put("admin", Arrays.asList("user:list", "user:add", "user:edit"));
        PERMISSIONS_CACHE.put("test", Collections.singletonList("user:list"));

    }
}

ShiroConfiguration

シロメイン構成情報は、この文書内に実装されています。

package com.battcn.config;

import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Shiro 配置
 *
 * @author Levin
 */
@Configuration
public class ShiroConfiguration {

    private static final Logger log = LoggerFactory.getLogger(ShiroConfiguration.class);

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

    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 加密器:这样一来数据库就可以是密文存储,为了演示我就不开启了
     *
     * @return HashedCredentialsMatcher
     */
//    @Bean
//    public HashedCredentialsMatcher hashedCredentialsMatcher() {
//        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//        //散列算法:这里使用MD5算法;
//        hashedCredentialsMatcher.setHashAlgorithmName("md5");
//        //散列的次数,比如散列两次,相当于 md5(md5(""));
//        hashedCredentialsMatcher.setHashIterations(2);
//        return hashedCredentialsMatcher;
//    }

    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        autoProxyCreator.setProxyTargetClass(true);
        return autoProxyCreator;
    }

    @Bean(name = "authRealm")
    public AuthRealm authRealm(EhCacheManager cacheManager) {
        AuthRealm authRealm = new AuthRealm();
        authRealm.setCacheManager(cacheManager);
        return authRealm;
    }

    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(AuthRealm authRealm) {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(authRealm);
        // <!-- 用户授权/认证信息Cache, 采用EhCache 缓存 -->
        defaultWebSecurityManager.setCacheManager(getEhCacheManager());
        return defaultWebSecurityManager;
    }

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

    /**
     * ShiroFilter<br/>
     * 注意这里参数中的 StudentService 和 IScoreDao 只是一个例子,因为我们在这里可以用这样的方式获取到相关访问数据库的对象,
     * 然后读取数据库相关配置,配置到 shiroFilterFactoryBean 的访问规则中。实际项目中,请使用自己的Service来处理业务逻辑。
     *
     * @param securityManager 安全管理器
     * @return ShiroFilterFactoryBean
     */
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 如果不设置默认会自动寻找Web工程根目录下的"/login"页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登录成功后要跳转的连接
        shiroFilterFactoryBean.setSuccessUrl("/index");
        shiroFilterFactoryBean.setUnauthorizedUrl("/denied");
        loadShiroFilterChain(shiroFilterFactoryBean);
        return shiroFilterFactoryBean;
    }

    /**
     * 加载shiroFilter权限控制规则(从数据库读取然后配置)
     */
    private void loadShiroFilterChain(ShiroFilterFactoryBean shiroFilterFactoryBean) {
        /////////////////////// 下面这些规则配置最好配置到配置文件中 ///////////////////////
        // TODO 重中之重啊,过滤顺序一定要根据自己需要排序
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 需要验证的写 authc 不需要的写 anon
        filterChainDefinitionMap.put("/resource/**", "anon");
        filterChainDefinitionMap.put("/install", "anon");
        filterChainDefinitionMap.put("/hello", "anon");
        // anon:它对应的过滤器里面是空的,什么都没做
        log.info("##################从数据库读取权限规则,加载到shiroFilter中##################");

        // 不用注解也可以通过 API 方式加载权限规则
        Map<String, String> permissions = new LinkedHashMap<>();
        permissions.put("/users/find", "perms[user:find]");
        filterChainDefinitionMap.putAll(permissions);
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    }
}

AuthRealm

上で紹介し  Realm 、安全な認証と権限検証プロセスは、コア書き換えする  AuthorizingRealm の doGetAuthenticationInfo(登录认证)doGetAuthorizationInfo(权限验证)

package com.battcn.config;

import com.battcn.entity.User;
import org.apache.shiro.SecurityUtils;
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.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.context.annotation.Configuration;

import java.util.*;

/**
 * 认证领域
 *
 * @author Levin
 * @version 2.5.1
 * @since 2018-01-10
 */
@Configuration
public class AuthRealm extends AuthorizingRealm {

    /**
     * 认证回调函数,登录时调用
     * 首先根据传入的用户名获取User信息;然后如果user为空,那么抛出没找到帐号异常UnknownAccountException;
     * 如果user找到但锁定了抛出锁定异常LockedAccountException;最后生成AuthenticationInfo信息,
     * 交给间接父类AuthenticatingRealm使用CredentialsMatcher进行判断密码是否匹配,
     * 如果不匹配将抛出密码错误异常IncorrectCredentialsException;
     * 另外如果密码重试此处太多将抛出超出重试次数异常ExcessiveAttemptsException;
     * 在组装SimpleAuthenticationInfo信息时, 需要传入:身份信息(用户名)、凭据(密文密码)、盐(username+salt),
     * CredentialsMatcher使用盐加密传入的明文密码和此处的密文密码进行匹配。
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
        String principal = (String) token.getPrincipal();
        User user = Optional.ofNullable(DBCache.USERS_CACHE.get(principal)).orElseThrow(UnknownAccountException::new);
        if (!user.isLocked()) {
            throw new LockedAccountException();
        }
        // 从数据库查询出来的账号名和密码,与用户输入的账号和密码对比
        // 当用户执行登录时,在方法处理上要实现 user.login(token)
        // 然后会自动进入这个类进行认证
        // 交给 AuthenticatingRealm 使用 CredentialsMatcher 进行密码匹配,如果觉得人家的不好可以自定义实现
        // TODO 如果使用 HashedCredentialsMatcher 这里认证方式就要改一下 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, "密码", ByteSource.Util.bytes("密码盐"), getName());
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, user.getPassword(), getName());
        Session session = SecurityUtils.getSubject().getSession();
        session.setAttribute("USER_SESSION", user);
        return authenticationInfo;
    }

    /**
     * 只有需要验证权限时才会调用, 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用.在配有缓存的情况下,只加载一次.
     * 如果需要动态权限,但是又不想每次去数据库校验,可以存在ehcache中.自行完善
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
        Session session = SecurityUtils.getSubject().getSession();
        User user = (User) session.getAttribute("USER_SESSION");
        // 权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        // 用户的角色集合
        Set<String> roles = new HashSet<>();
        roles.add(user.getRoleName());
        info.setRoles(roles);
        // 用户的角色对应的所有权限,如果只使用角色定义访问权限,下面可以不要
        // 只有角色并没有颗粒度到每一个按钮 或 是操作选项  PERMISSIONS 是可选项
        final Map<String, Collection<String>> permissionsCache = DBCache.PERMISSIONS_CACHE;
        final Collection<String> permissions = permissionsCache.get(user.getRoleName());
        info.addStringPermissions(permissions);
        return info;
    }
}

コントローラ

では   構成に代え  、それは何の認証もこの方法に加えて、アクセスできないことを意味し  、また、いくつかのノートに私たちがするための関連する方法を提供します...ShiroConfigurationshiroFilter/hello = anonShiro

共通アノテーション

  • @RequiresGuest  代表は認証なしでアクセスすることができ、共感があります /path = anon
  • @RequiresAuthenticationの  認証を使用すると、動作できるようにした後に限り、ログインが成功するよう、要求され
  • @RequiresPermissionsは、  特定の権限を必要とし、それがスローされませんAuthorizationException
  • @RequiresRolesが  スローされていない、特定のオレンジを要求しますAuthorizationException
  • @RequiresUser  明確ではない、それが一般的に使用されていません...

LoginController

package com.battcn.controller;

import com.battcn.config.ShiroConfiguration;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

/**
 * @author Levin
 * @since 2018/6/28 0028
 */
@RestController
public class LoginController {

    private static final Logger log = LoggerFactory.getLogger(ShiroConfiguration.class);

    @GetMapping(value = "/hello")
    public String hello() {
        log.info("不登录也可以访问...");
        return "hello...";
    }

    @GetMapping(value = "/index")
    public String index() {
        log.info("登陆成功了...");
        return "index";
    }

    @GetMapping(value = "/denied")
    public String denied() {
        log.info("小伙子权限不足,别无谓挣扎了...");
        return "denied...";
    }

    @GetMapping(value = "/login")
    public String login(String username, String password, RedirectAttributes model) {
        // 想要得到 SecurityUtils.getSubject() 的对象..访问地址必须跟 shiro 的拦截地址内.不然后会报空指针
        Subject sub = SecurityUtils.getSubject();
        // 用户输入的账号和密码,,存到UsernamePasswordToken对象中..然后由shiro内部认证对比,
        // 认证执行者交由 com.battcn.config.AuthRealm 中 doGetAuthenticationInfo 处理
        // 当以上认证成功后会向下执行,认证失败会抛出异常
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            sub.login(token);
        } catch (UnknownAccountException e) {
            log.error("对用户[{}]进行登录验证,验证未通过,用户不存在", username);
            token.clear();
            return "UnknownAccountException";
        } catch (LockedAccountException lae) {
            log.error("对用户[{}]进行登录验证,验证未通过,账户已锁定", username);
            token.clear();
            return "LockedAccountException";
        } catch (ExcessiveAttemptsException e) {
            log.error("对用户[{}]进行登录验证,验证未通过,错误次数过多", username);
            token.clear();
            return "ExcessiveAttemptsException";
        } catch (AuthenticationException e) {
            log.error("对用户[{}]进行登录验证,验证未通过,堆栈轨迹如下", username, e);
            token.clear();
            return "AuthenticationException";
        }
        return "success";
    }
}

UserControllerで

package com.battcn.controller;

import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author Levin
 * @since 2018/6/28 0028
 */
@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping
    public String get() {
        return "get.....";
    }

    /**
     * RequiresRoles 是所需角色 包含 AND 和 OR 两种
     * RequiresPermissions 是所需权限 包含 AND 和 OR 两种
     *
     * @return msg
     */
    @RequiresRoles(value = {"admin", "test"}, logical = Logical.OR)
    //@RequiresPermissions(value = {"user:list", "user:query"}, logical = Logical.OR)
    @GetMapping("/query")
    public String query() {
        return "query.....";
    }

    @GetMapping("/find")
    public String find() {
        return "find.....";
    }
}

主な機能

package com.battcn;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author Levin
 */
@SpringBootApplication
public class Chapter25Application {

    public static void main(String[] args) {

        SpringApplication.run(Chapter25Application.class, args);

    }
}

テスト

開始  Chapter25Application.java に  main 良く開くためにここに効果を発揮するためには、この方法  postman だけプロセスを実証しており、実行するテストをし、残りはテストするために独自のコードをコピーすることができます...

ログインする  u3 には  DBCache 持っている役割  testだけ  user:list この1つの権限を

SpringBoot統合史郎セキュリティフレームワーク
着陸

アクセス  /users/query 我々は、応答/権限の役割と一致しているので、成功を

SpringBoot統合史郎セキュリティフレームワーク
アクセスQueryji

アクセスが  /users/find 失敗した、とにリダイレクト  /denied 、なぜ質問インターフェース  /users/find 一切の権限も不十分なノートを書かないためには?

SpringBoot統合史郎セキュリティフレームワーク
不十分な権限

慎重な友人は確かにあります  (「/ユーザ/見つける」ShiroConfigurationでpermissions.putで書かれた 「パーマ[利用者:見つける]」);  意味、私たちだけでなく、ノートの道でも、データベースの初期化によってロードすることができますすることができますツリーコントロールを行うためのアクセス許可は、次のようになり....

おすすめ

転載: blog.51cto.com/14230003/2418778