春BOOT2(XII):手のタッチの手は史郎セキュリティフレームワークを構築するためにあなたを教えるために

I.はじめに

SpringBoot +史郎+ MyBatisのが完了しました。

四郎は、少しチュートリアルパートナーを見て追随して前に、私は多くのピットを見つける会った( 'இ料理をஇ `)

順番を少し変更し、正常に実行されました。あなたは郵便配達によって試験することができます

小さなビビ∠(ᐛ「∠)_、直接ソースコードにします。https://github.com/niaobulashi/spring-boot-learning/tree/master/spring-boot-20-shiro

二、史郎が有効です

Apacheの四郎は強力で柔軟、オープンソースのセキュリティフレームワークです。あなたはきれいに認証、許可、セッション管理と企業の暗号化を処理することができます。

二、史郎がやってすることができます

  • ユーザーを認証
  • など、ユーザーのアクセス制御、:1.ユーザーは、特定のセキュリティロールが割り当てられている場合。図2は、ユーザーが操作を完了するための権限を付与されているかどうかを判断します
  • セッションAPIは、任意に非WebまたはEJBコンテナの環境で使用することができます
  • レスポンス認証、アクセス制御、またはイベントセッションのライフサイクル
  • 1つまたは複数のユーザのデータソースのセキュリティデータは、複合ユーザ「ビュー」(ビュー)に結合されてもよいです
  • シングルサインオン(SSO)機能をサポート
  • ログオンしなくても、「私を忘れないでください」サービス、ユーザ関連情報へのアクセスの提供をサポートしています

図シロフレーム次のように

  • 認証(認証):ユーザ識別、一般ユーザ「ログイン」と呼ばれます
  • 許可(承認):アクセス制御。例えば、ユーザが操作を使用する許可を持っている場合。
  • セッション管理(セッション管理):でも非EJBまたはWebアプリケーションにおけるユーザー固有のセッション管理、。
  • 暗号化(暗号化):使いやすさを確保するために、データソースを暗号化する暗号化アルゴリズムを使用している間。

件名、セキュリティマネージャとレルム:概念レベルでは、シロ・アーキテクチャは、3つの主要なアイデアから成ります。次の図は、これらの成分が相互作用、私たちは順番に説明する方法を示しています。

IMG

  • 件名:現在のユーザーは、件名には、1人でもよいが、また、サードパーティのサービス、デーモンアカウント、cronジョブまたはその他のかもしれ - 任意の現在のイベントやソフトウェアとの相互作用。
  • セキュリティマネージャ:内部の安全コンポーネントが一緒にセキュリティ傘を構成すると、すべての件名を管理、セキュリティマネージャは、シロコアアーキテクチャです。
  • レルム:情報を確認するために使用権限を、私たちは実現を所有しています。レルムは、特定のセキュリティDAOに本質的である:データパッケージに接続されたソースの詳細については、士郎に必要な関連データを取得します。史郎を設定する際には、認証(認証)および/または許可(承認)を実装するには、少なくとも1つのレルムを指定する必要があります。

第三に、コードの実装

1、Mavenのは、依存している追加

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

2、設定ファイル

application.yml

# 服务器端口
server:
  port: 8081

# 配置Spring相关信息
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: root
    password: root

# 配置Mybatis
mybatis:
  type-aliases-package: com.niaobulashi.model
  mapper-locations: classpath:mapper/*.xml
  configuration:
    # 开启驼峰命名转换
    map-underscore-to-camel-case: true

# 打印SQL日志
logging:
  level:
    com.niaobulashi.mapper: DEBUG

マッパーを追加する方法のスキャンを開始、私は通常、別の文は、各マッパーにスキャンが必要です特に明記法上の開始します

@SpringBootApplication
@MapperScan("com.niaobulashi.mapper")
public class ShiroApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShiroApplication.class, args);
    }
}

3、シンプルなテーブルデザイン

ユーザテーブル、テーブルの役割、権限テーブル、テーブルのユーザーの役割、役割権限テーブル:5つのテーブル以外の何物でもありません。

以下、この絵を見て、それは非常に明確にすることができます。

特に、私は彼らがあまりにも多くのスペースを取り、出しません。直接接続リンクします。https://github.com/niaobulashi/spring-boot-learning/blob/master/spring-boot-20-shiro/db/test.sql

4、エンティティ・クラス

User.java

@Data
public class User implements Serializable {

    private static final long serialVersionUID = -6056125703075132981L;

    private Integer id;

    private String account;

    private String password;

    private String username;
}

Role.java

@Data
public class Role implements Serializable {

    private static final long serialVersionUID = -1767327914553823741L;

    private Integer id;

    private String role;

    private String desc;
}

5、マッパ層

ここでは、要約:データベース操作に関連する簡単なユーザのログイン権限史郎制御は、主に土です

  • ユーザーのログイン名は、照会し、ユーザー情報を
  • よると、ユーザーのクエリの役割情報
  • 根据角色查询权限信息

UserMapper.java/UserMapper.xml

public interface UserMapper {
    /**
     * 根据账户查询用户信息
     * @param account
     * @return
     */
    User findByAccount(@Param("account") String account);
}
<!--用户表结果集-->
<sql id="base_column_list">
    id, account, password, username
</sql>

<!--根据账户查询用户信息-->
<select id="findByAccount" parameterType="Map" resultType="com.niaobulashi.model.User">
    select
    <include refid="base_column_list"/>
    from user
    where account = #{account}
</select>

RoleMapper.java/RoleMapper.xml

public interface RoleMapper {
    /**
     * 根据userId查询角色信息
     * @param userId
     * @return
     */
    List<Role> findRoleByUserId(@Param("userId") Integer userId);
}
<!--角色表字段结果集-->
<sql id="base_cloum_list">
    id, role, desc
</sql>

<!--根据userId查询角色信息-->
<select id="findRoleByUserId" parameterType="Integer" resultType="com.niaobulashi.model.Role">
    select r.id, r.role
    from role r
    left join user_role ur on ur.role_id = r.id
    left join user u on u.id = ur.user_id
    where 1=1
    and u.user_id = #{userId}
</select>

PermissionMapper.java/PermissionMapper.xml

public interface PermissionMapper {
    /**
     * 根据角色id查询权限
     * @param roleIds
     * @return
     */
    List<String> findByRoleId(@Param("roleIds") List<Integer> roleIds);
}
<!--权限查询结果集-->
<sql id="base_column_list">
    id, permission, desc
</sql>

<!--根据角色id查询权限-->
<select id="findByRoleId" parameterType="List" resultType="String">
    select permission
    from permission, role_permission rp
    where rp.permission_id = permission.id and rp.role_id in
    <foreach collection="roleIds" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</select>

6、Service层

没有其他逻辑,只有继承。

注意:

不过需要注意的一点是,我在Service层中,使用的注解@Service:启动时会自动注册到Spring容器中。

否则启动时,拦截器配置初始化时,会找不到Service。。。这点有点坑。

UserService.java/UserServiceImpl.java

public interface UserService {
    /**
     * 根据账户查询用户信息
     * @param account
     * @return
     */
    User findByAccount(String account);
}
@Service("userService")
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    /**
     * 根据账户查询用户信息
     * @param account
     * @return
     */
    @Override
    public User findByAccount(String account) {
        return userMapper.findByAccount(account);
    }
}

RoleService.java/RoleServiceImpl.java

public interface RoleService {

    /**
     * 根据userId查询角色信息
     * @param id
     * @return
     */
    List<Role> findRoleByUserId(Integer id);
}
@Service("roleService")
public class RoleServiceImpl implements RoleService {

    @Resource
    private RoleMapper roleMapper;

    /**
     * 根据userId查询角色信息
     * @param id
     * @return
     */
    @Override
    public List<Role> findRoleByUserId(Integer id) {
        return roleMapper.findRoleByUserId(id);
    }
}

PermissionService.java/PermissionServiceImpl.java

public interface PermissionService {

    /**
     * 根据角色id查询权限
     * @param roleIds
     * @return
     */
    List<String> findByRoleId(@Param("roleIds") List<Integer> roleIds);
}
@Service("permissionService")
public class PermissionServiceImpl implements PermissionService {

    @Resource
    private PermissionMapper permissionMapper;

    /**
     * 根据角色id查询权限
     * @param roleIds
     * @return
     */
    @Override
    public List<String> findByRoleId(List<Integer> roleIds) {
        return permissionMapper.findByRoleId(roleIds);
    }
}

7、系统统一返回状态枚举和包装方法

状态字段枚举

StatusEnmus.java

public enum StatusEnums {

    SUCCESS(200, "操作成功"),
    SYSTEM_ERROR(500, "系统错误"),
    ACCOUNT_UNKNOWN(500, "账户不存在"),
    ACCOUNT_IS_DISABLED(13, "账号被禁用"),
    INCORRECT_CREDENTIALS(500,"用户名或密码错误"),
    PARAM_ERROR(400, "参数错误"),
    PARAM_REPEAT(400, "参数已存在"),
    PERMISSION_ERROR(403, "没有操作权限"),
    NOT_LOGIN_IN(15, "账号未登录"),
    OTHER(-100, "其他错误");

    @Getter
    @Setter
    private int code;
    @Getter
    @Setter
    private String message;

    StatusEnums(int code, String message) {
        this.code = code;
        this.message = message;
    }
}

响应包装方法

ResponseCode.java

@Data
@AllArgsConstructor
public class ResponseCode<T> implements Serializable {

    private Integer code;
    private String message;
    private Object data;

    private ResponseCode(StatusEnums responseCode) {
        this.code = responseCode.getCode();
        this.message = responseCode.getMessage();
    }

    private ResponseCode(StatusEnums responseCode, T data) {
        this.code = responseCode.getCode();
        this.message = responseCode.getMessage();
        this.data = data;
    }

    private ResponseCode(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    /**
     * 返回成功信息
     * @param data      信息内容
     * @param <T>
     * @return
     */
    public static<T> ResponseCode success(T data) {
        return new ResponseCode<>(StatusEnums.SUCCESS, data);
    }

    /**
     * 返回成功信息
     * @return
     */
    public static ResponseCode success() {
        return new ResponseCode(StatusEnums.SUCCESS);
    }

    /**
     * 返回错误信息
     * @param statusEnums      响应码
     * @return
     */
    public static ResponseCode error(StatusEnums statusEnums) {
        return new ResponseCode(statusEnums);
    }
}

8、Shiro配置

ShiroConfig.java

@Configuration
public class ShiroConfig {

    /**
     * 路径过滤规则
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setSuccessUrl("/");
        // 拦截器
        Map<String, String> map = new LinkedHashMap<>();
        // 配置不会被拦截的链接 顺序判断
        map.put("/login", "anon");
        // 过滤链定义,从上向下顺序执行,一般将/**放在最为下边
        // 进行身份认证后才能访问
        // authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
        map.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    /**
     * 自定义身份认证Realm(包含用户名密码校验,权限校验等)
     * @return
     */
    @Bean
    public AuthRealm authRealm() {
        return new AuthRealm();
    }

    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(authRealm());
        return securityManager;
    }

    /**
     * 开启Shiro注解模式,可以在Controller中的方法上添加注解
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

扩展:权限拦截Filter的URL的一些说明

这里扩展一下权限拦截Filter的URL的一些说明

1、URL匹配规则

(1)“?”:匹配一个字符,如”/admin?”,将匹配“ /admin1”、“/admin2”,但不匹配“/admin”

(2)“”:匹配零个或多个字符串,如“/admin”,将匹配“ /admin”、“/admin123”,但不匹配“/admin/1”

(3)“”:匹配路径中的零个或多个路径,如“/admin/”,将匹配“/admin/a”、“/admin/a/b”

2、shiro过滤器

Filter 解释
anon 无参,开放权限,可以理解为匿名用户或游客
authc 无参,需要认证
logout 无参,注销,执行后会直接跳转到shiroFilterFactoryBean.setLoginUrl(); 设置的 url
authcBasic 无参,表示 httpBasic 认证
user 无参,表示必须存在用户,当登入操作时不做检查
ssl 无参,表示安全的URL请求,协议为 https
perms[user] 参数可写多个,表示需要某个或某些权限才能通过,多个参数时写 perms["user, admin"],当有多个参数时必须每个参数都通过才算通过
roles[admin] 参数可写多个,表示是某个或某些角色才能通过,多个参数时写 roles["admin,user"],当有多个参数时必须每个参数都通过才算通过
rest[user] 根据请求的方法,相当于 perms[user:method],其中 method 为 post,get,delete 等
port[8081] 当请求的URL端口不是8081时,跳转到schemal://serverName:8081?queryString 其中 schmal 是协议 http 或 https 等等,serverName 是你访问的 Host,8081 是 Port 端口,queryString 是你访问的 URL 里的 ? 后面的参数

常用的主要就是 anon,authc,user,roles,perms 等

:アノン、authcはは、authcBasicは、ユーザ認証がフィルタ、パーマ、ポート、残りの役割の最初のセットであり 、SSL 、それは最初のログイン認証動作(すなわち、第一完了しなければならない第二のフィルタグループ許可、フィルターを通して認可あなたが上陸していない場合、このようなアクセス許可必要なロールURLとして認可デバイス(の第2のセットを取ること)を見つけるために行くために認定認証を完了させるために、それはに直接ジャンプしますshiroFilterFactoryBean.setLoginUrl();)URL設定。

9、カスタム・レルム

主な後継者AuthorizingRealm、メソッドのオーバーライドdoGetAuthorizationInfodoGetAuthenticationInfo

認証:doGetAuthorizationInfo

認定:doGetAuthenticationInfo

AuthRealm.java

public class AuthRealm extends AuthorizingRealm {

    @Resource
    private UserService userService;

    @Resource
    private RoleService roleService;

    @Resource
    private PermissionService permissionService;

    /**
     * 授权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        User user = (User) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        // 根据用户Id查询角色信息
        List<Role> roleList = roleService.findRoleByUserId(user.getId());
        Set<String> roleSet = new HashSet<>();
        List<Integer> roleIds = new ArrayList<>();
        for (Role role : roleList) {
            roleSet.add(role.getRole());
            roleIds.add(role.getId());
        }
        // 放入角色信息
        authorizationInfo.setRoles(roleSet);
        // 放入权限信息
        List<String> permissionList = permissionService.findByRoleId(roleIds);
        authorizationInfo.setStringPermissions(new HashSet<>(permissionList));

        return authorizationInfo;
    }

    /**
     * 认证
     * @param authToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authToken;
        // 根据用户名查询用户信息
        User user = userService.findByAccount(token.getUsername());
        if (user == null) {
            return null;
        }
        return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
    }
}

10、Contrller層

@RestController
public class LoginController {

    /**
     * 登录操作
     * @param user
     * @return
     */
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public ResponseCode login(@RequestBody User user) {
        Subject userSubject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(user.getAccount(), user.getPassword());
        try {
            // 登录验证
            userSubject.login(token);
            return ResponseCode.success();
        } catch (UnknownAccountException e) {
            return ResponseCode.error(StatusEnums.ACCOUNT_UNKNOWN);
        } catch (DisabledAccountException e) {
            return ResponseCode.error(StatusEnums.ACCOUNT_IS_DISABLED);
        } catch (IncorrectCredentialsException e) {
            return ResponseCode.error(StatusEnums.INCORRECT_CREDENTIALS);
        } catch (Throwable e) {
            e.printStackTrace();
            return ResponseCode.error(StatusEnums.SYSTEM_ERROR);
        }
    }


    @GetMapping("/login")
    public ResponseCode login() {
        return ResponseCode.error(StatusEnums.NOT_LOGIN_IN);
    }

    @GetMapping("/auth")
    public String auth() {
        return "已成功登录";
    }

    @GetMapping("/role")
    @RequiresRoles("vip")
    public String role() {
        return "测试Vip角色";
    }

    @GetMapping("/permission")
    @RequiresPermissions(value = {"add", "update"}, logical = Logical.AND)
    public String permission() {
        return "测试Add和Update权限";
    }

    /**
     * 登出
     * @return
     */
    @GetMapping("/logout")
    public ResponseCode logout() {
        getSubject().logout();
        return ResponseCode.success();
    }
}

第四に、テスト

1.ログインします。http:// localhost:8081 /ログイン

{
    "account":"123",
    "password":"232"
}

2は、他のラインで直接送信されたリクエストのURLを取得することです。

インターフェイスのテストが合格した、食べても安全であることができます。


推奨読書:

張Kaitao古い「史郎私に従っ」https://jinnianshilongnian.iteye.com/blog/2018936

おすすめ

転載: www.cnblogs.com/niaobulashi/p/springboot-shiro.html