欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring?Boot在Web應用中基于JdbcRealm安全驗證過程

 更新時間:2023年02月10日 14:15:23   作者:Samson_bu  
這篇文章主要為大家介紹了Spring?Boot在Web應用中基于JdbcRealm安全驗證過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪<BR>

正文

在安全領域,Subject 用來指代與系統(tǒng)交互的實體,可以是用戶、第三方應用等,它是安全認證框架(例如 Shiro)驗證的主題。 Principal 是 Subject 具有的屬性,例如用戶名、身份證號、電話號碼、郵箱等任何安全驗證過程中關心的要素。 Primary principal 指能夠唯一區(qū)分 Subject 的屬性,例如身份證號碼,論壇系統(tǒng)中的登錄名等,通過它可以唯一識別一個 Subject。 Credential 是認證過程中與 Principal 一同提交到系統(tǒng)的信息,通常是只有 Subject 知道的加密信息,例如密碼、PGP Key等。

應用系統(tǒng)或者說安全認證框架驗證一個 Subject 的過程為:

  • Subject 提供 principal(例如用戶名)和 credential(例如密碼)
  • 安全認證框架(例如 Shiro)會驗證 Subject 提供的信息與保存在應用系統(tǒng)中的信息(例如存儲在數(shù)據(jù)庫或者 LDAP 中)是否匹配。 若匹配,則認為 Subject 為合法用戶;否則,為非法用戶。

01-RBAC 基于角色的訪問控制

Role-Based Access Control(RBAC)是最普遍的權(quán)限設計模型。 它包含了三個實體:

  • 用戶
  • 角色
  • 權(quán)限

我們定義三張表,來存儲這三個實體:

CREATE TABLE `demo_user` (
  `user_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '編號',
  `username` varchar(20) NOT NULL COMMENT '帳號',
  `password` varchar(32) NOT NULL COMMENT '密碼MD5(密碼+鹽)',
  `locked` tinyint(4) DEFAULT NULL COMMENT '狀態(tài)(0:正常,1:鎖定)',
  `ctime` bigint(20) DEFAULT NULL COMMENT '創(chuàng)建時間',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用戶';
-- 插入兩條數(shù)據(jù),表示兩個用戶
INSERT INTO demo_user (user_id, username, password, locked, ctime) 
VALUES (1, 'admin', 'admin', '0', sysdate()),
       (2, 'lihua', 'lihua123', '0', sysdate()),
       (3, 'hanmeimei', 'hanmeimei123', '0', sysdate());
CREATE TABLE `demo_role` (
  `role_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '編號',
  `name` varchar(20) DEFAULT NULL COMMENT '角色名稱',
  `description` varchar(1000) DEFAULT NULL COMMENT '角色描述',
  PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色';
-- 插入兩條數(shù)據(jù),表示兩個角色
INSERT INTO demo_role(role_id, name, description) 
VALUES (1, 'admin', '管理員'),
       (2, 'user', '普通用戶');
CREATE TABLE `demo_permission` (
  `permission_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '編號',
  `name` varchar(20) DEFAULT NULL COMMENT '名稱',
  `permission_value` varchar(50) DEFAULT NULL COMMENT '權(quán)限值',
  `status` tinyint(4) DEFAULT NULL COMMENT '狀態(tài)(0:禁止,1:正常)',
  PRIMARY KEY (`permission_id`)
) ENGINE=InnoDB AUTO_INCREMENT=86 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='權(quán)限';
-- 插入三條數(shù)據(jù),表示三種不同的權(quán)限
INSERT INTO demo_permission(permission_id, name, permission_value, status) 
VALUES (1, '新增用戶', 'user:add', 1),
       (2, '刪除用戶', 'user:delete', 1),
       (3, '查看用戶', 'user:get', 1);

實體之間具有如下的關系:

  • 角色權(quán)限,一對多,一個角色可以具有多個權(quán)限。
  • 用戶角色,一對多,一個用戶可以具有多個角色。
  • 用戶權(quán)限,一對多,一個用戶有多個權(quán)限。權(quán)限的來源有兩種,一類是直接賦予它某些權(quán)限,另一類是通過賦予它多個角色而賦予它角色關聯(lián)的權(quán)限。

我們定義三張表,來存儲上述三種關系:

CREATE TABLE `demo_role_permission` (
  `role_permission_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '編號',
  `role_id` int(10) unsigned NOT NULL COMMENT '角色編號',
  `permission_id` int(10) unsigned NOT NULL COMMENT '權(quán)限編號',
  PRIMARY KEY (`role_permission_id`)
) ENGINE=InnoDB AUTO_INCREMENT=129 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色權(quán)限關聯(lián)表';
-- 插入四條條數(shù)據(jù),admin 具有增、刪、查用戶權(quán)限,user 具有查用戶權(quán)限
INSERT INTO demo_role_permission(role_permission_id, role_id, permission_id) 
VALUES (1, 1, 1),
       (2, 1, 2),
       (3, 1, 3),
       (4, 2, 3);
CREATE TABLE `demo_user_role` (
  `user_role_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '編號',
  `user_id` int(10) unsigned NOT NULL COMMENT '用戶編號',
  `role_id` int(10) DEFAULT NULL COMMENT '角色編號',
  PRIMARY KEY (`user_role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用戶角色關聯(lián)表';
-- 插入三條數(shù)據(jù)
INSERT INTO demo_user_role (user_role_id, user_id, role_id) 
VALUES (1, 1, 1),
       (2, 2, 2),
       (3, 3, 2);
CREATE TABLE `demo_user_permission` (
  `user_permission_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '編號',
  `user_id` int(10) unsigned NOT NULL COMMENT '用戶編號',
  `permission_id` int(10) unsigned NOT NULL COMMENT '權(quán)限編號',
  PRIMARY KEY (`user_permission_id`)
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用戶權(quán)限關聯(lián)表';
-- 插入五條數(shù)據(jù) 
INSERT INTO demo_user_permission (user_permission_id, user_id, permission_id) 
VALUES (1, 1, 1),
       (2, 1, 2),
       (3, 1, 3),
       (4, 2, 3),
       (5, 3, 3);

02-Shiro 中基于 JdbcRealm 實現(xiàn)用戶認證、授權(quán)

Shiro 中 Realm 是負責與應用系統(tǒng)中的權(quán)限模型打交道的組件,所以它也被稱為 Security DAO(Data Access Object)。 Shiro 中 Realm 的類型設計結(jié)構(gòu)圖如下所示:

AuthenticatingRealm 和 AuthorizingRealm 分別實現(xiàn)了認證、授權(quán)的整體流程,將如何獲取存儲認證信息、權(quán)限信息通過模板方法方式留給派生類去實現(xiàn):

  • AuthenticatingRealm#doGetAuthenticationInfo,如何獲取系統(tǒng)存儲的認證信息,例如用戶、密碼等。對應上節(jié)中的 demo_user 表。
  • AuthorizingRealm#doGetAuthorizationInfo,如何獲得用戶的角色、權(quán)限信息,對應上節(jié)中的 demo_role、demo_permission 表。

接下來,我詳細分析下 Shiro 提供的一個基于數(shù)據(jù)庫的實現(xiàn)類 JdbcRealm。 簡化后的 doGetAuthenticationInfo 流程:

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    // 用戶提交上來的信息
    String username = ((UsernamePasswordToken) token).getUsername();
    Connection conn = null;
    SimpleAuthenticationInfo info = null;
    try {
        conn = dataSource.getConnection(); // 數(shù)據(jù)庫引用
        // 從數(shù)據(jù)庫中獲取密碼 
        String password = getPasswordForUser(conn, username)[0];    // 關鍵點1
        // 創(chuàng)建驗證結(jié)果
        info = new SimpleAuthenticationInfo(username, password.toCharArray(), getName());
    } catch (SQLException e) {
        final String message = "There was a SQL error while authenticating user [" + username + "]";
        throw new AuthenticationException(message, e);
    } finally {
        JdbcUtils.closeConnection(conn);
    }
    return info;
}

簡化后的 doGetAuthorizationInfo 方法:

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    // 這里的 principals 主要為獲得用戶名
    String username = (String) getAvailablePrincipal(principals);
    Connection conn = null;
    Set<String> roleNames = null;
    Set<String> permissions = null;
    try {
        conn = dataSource.getConnection();  // 數(shù)據(jù)庫引用
        // Retrieve roles and permissions from database
        roleNames = getRoleNamesForUser(conn, username);  // 關鍵點2
        if (permissionsLookupEnabled) {   
            permissions = getPermissions(conn, username, roleNames);  // 關鍵點3
        }
    } catch (SQLException e) {
        final String message = "There was a SQL error while authorizing user [" + username + "]";
        throw new AuthorizationException(message, e);
    } finally {
        JdbcUtils.closeConnection(conn);
    }
    // 創(chuàng)建權(quán)限信息
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);
    info.setStringPermissions(permissions);
    return info;
}

注:從 JdbcRealm 的源碼中能夠發(fā)現(xiàn)如下幾點:

  • 關鍵點1,需要一個 SQL 語句,從數(shù)據(jù)庫表中查詢用戶的密碼。結(jié)合上節(jié)中定義的表,這個 SQL 語句為 SELECT password FROM demo_user WHERE username = ?
  • 關鍵點2,需要從數(shù)據(jù)庫中查詢用戶的角色,SQL 語句為 SELECT name FROM demo_user_role ur, demo_user u, demo_role r WHERE ur.user_id = u.user_id AND ur.role_id = r.role_id AND u.username = ?。
  • 關鍵點3,需要根據(jù)角色列表從數(shù)據(jù)庫中查詢角色具備的權(quán)限集合,SQL 語句為 SELECT permission_value FROM demo_role_permission rp, demo_role r, demo_permission p WHERE rp.role_id = r.role_id AND rp.permission_id = p.permission_id AND r.name = ?。
  • 其他點,我們需要一個數(shù)據(jù)庫引用,即 DataSource 對象。要查詢權(quán)限,需要 permissionsLookupEnabled == true。

綜上,我們需要對 JdbcRealm 對象進行設置,使其能夠獲取到我們存儲在數(shù)據(jù)庫中的信息。

@Bean
public Realm realm(@Autowired DataSource dataSource) {
    final JdbcRealm jdbcRealm = new JdbcRealm();
    jdbcRealm.setDataSource(dataSource);
    String authQuery = "SELECT password FROM demo_user WHERE username = ?";
    jdbcRealm.setAuthenticationQuery(authQuery);
    jdbcRealm.setPermissionsLookupEnabled(true);
    String roleQuery = "SELECT name FROM demo_user_role ur, demo_user u, demo_role r WHERE ur.user_id = u.user_id AND ur.role_id = r.role_id AND u.username = ?";
    String permissionQuery = "SELECT permission_value FROM demo_role_permission rp, demo_role r, demo_permission p WHERE rp.role_id = r.role_id AND rp.permission_id = p.permission_id AND r.name = ?";
    jdbcRealm.setUserRolesQuery(roleQuery);
    jdbcRealm.setPermissionsQuery(permissionQuery);
    return jdbcRealm;
}

除了使用 JdbcRealm 的方法外,還可以仿照它編寫我們自己的實現(xiàn)。接下來,我將結(jié)合 Spring Data JPA 編寫一個 JpaRealm。

public class JpaRealm extends AuthorizingRealm {
    @Autowired
    private DemoUserRepository userRepository;
    @Autowired
    private DemoUserRoleRepository userRoleRepository;
    @Autowired
    private DemoRolePermissionRepository rolePermissionRepository;
    @Autowired
    private DemoPermissionRepository permissionRepository;
    @Autowired
    private DemoRoleRepository roleRepository;
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 查詢權(quán)限的過程與 JdbcRealm 一樣,只不過使用了 jpa 
        //null usernames are invalid
        if (principals == null) {
            throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
        }
        String username = (String) getAvailablePrincipal(principals);
        final DemoUser user = this.userRepository.findByUsername(username)
                .orElseThrow(() -> new UnknownAccountException("No account found for user [" + username + "]"));
        final List<Integer> roleIds = userRoleRepository.findByUserId(user.getUserId()).stream()
                .map(DemoUserRole::getUserRoleId)
                .collect(Collectors.toList());
        final Set<String> roleNames = roleRepository.findAllById(roleIds).stream()
                .map(DemoRole::getName)
                .collect(Collectors.toSet());
        final List<Integer> permissionIds = rolePermissionRepository.findAllByRoleIdIn(roleIds).stream()
                .map(DemoRolePermission::getPermissionId)
                .collect(Collectors.toList());
        final Set<String> permissions = permissionRepository.findAllById(permissionIds).stream()
                .map(DemoPermission::getPermissionValue)
                .collect(Collectors.toSet());
        final SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);
        info.setStringPermissions(permissions);
        return info;
    }
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 查詢身份信息的過程與 JdbcRealm 一樣
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = upToken.getUsername();
        // Null username is invalid
        if (username == null) {
            throw new AccountException("Null usernames are not allowed by this realm.");
        }
        try {
            final DemoUser user = this.userRepository.findByUsername(username)
                    .orElseThrow(() -> new UnknownAccountException("No account found for user [" + username + "]"));
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, user.getPassword().toCharArray(), getName());
            return info;
        } catch (Throwable t) {
            final String message = "There was a SQL error while authenticating user [" + username + "]";
            // Rethrow any SQL errors as an authentication exception
            throw new AuthenticationException(message, t);
        }
    }
}

在之前初始化 JdbcRealm 的地方,換成 JpaRealm 就可以了。

@Bean
public Realm jpaRealm() {
    return new JpaRealm();
}

03-集成到 Spring Boot Web 應用中

接下來,我把前兩節(jié)的東西整合在一個 Spring Boot Web 應用中,并測試下效果吧。

首先,編寫兩個 Controller 類,以便能夠從瀏覽器或 Postman 中訪問它:

@RestController
public class LoginController {
    @GetMapping("/login")
    public String login() {
        return "please login!";
    }
    @GetMapping("/index")
    public String index() {
        final Subject subject = SecurityUtils.getSubject();
        if (subject.isAuthenticated()) {
            final Object principal = subject.getPrincipal();
            return "hello, " + principal;
        }
        return "hello";
    }
}

LoginController 負責處理到 "/login" 和 "/index' 的請求,主要是為了匹配 Shiro 中的 loginUrl 和 successfulUrl。

shiro:
  enabled: true
  web:
    enabled: true
  loginUrl: /login
  successUrl: /index

當未登錄用戶訪問時,會重定向到 "/login",你可以看到一個請求登錄的提示。 登錄成功后,會重定向到 "/index" ,并顯示 "hello, ${用戶名}" 提示。

注:為了偷懶,我沒有寫登錄界面,默認情況下 Shiro 中的 AuthenticatingFilter 會處理到 loginUrl 的 POST 請求,并從中提取 principal 來進行登錄驗證。

// org.apache.shiro.web.filter.AccessControlFilter.isLoginRequest
protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {
    return pathsMatch(getLoginUrl(), request);
}

所以,當需要登錄時,只需要向 "/login" 發(fā)送一個 POST 請求即可,例如:

curl --location --request POST 'http://localhost:18886/shiro-web/login' \
--form 'username="lihua"' \
--form 'password="lihua123"'

然后,我編寫了另一個 Controller 它主要用來實現(xiàn)對 User 的增刪查操作:

@RestController
public class UserController {
    @Autowired
    private DemoUserRepository userRepository;
    @RequiresPermissions("user:get")
    @GetMapping("/users")
    public List<DemoUser> all() {
        final List<DemoUser> all = userRepository.findAll();
        return all;
    }
    @RequiresPermissions("user:get")
    @GetMapping("/users/{id}")
    public DemoUser one(@PathVariable Integer id) {
        final Optional<DemoUser> byId = userRepository.findById(id);
        return byId.orElse(null);
    }
    @RequiresPermissions("user:add")
    @PostMapping("/users")
    public String add(@RequestBody DemoUser user) {
        userRepository.save(user);
        return "success";
    }
    @RequiresPermissions("user:delete")
    @DeleteMapping("/users/{id}")
    public String delete(@PathVariable Integer id) {
        userRepository.deleteById(id);
        return "success";
    }
}

加上之前的代碼,所有的元素我們就湊齊了,可以 run 起來檢查一下了。 如果需要完整的源代碼,可以在我的 gitee.com 上下載。

04-總結(jié)

今天,我介紹了如何使用 Shiro 中提供的 JdbcRealm 實現(xiàn)基于 RBAC 模型的權(quán)限驗證。 之后,又仿照 JdbcRealm 實現(xiàn)了一個基于 JPA 的 Realm 實現(xiàn),并將它們集成在了一個 Web 應用中進行了驗證。 希望今天的內(nèi)容能對你有所幫助。

以上就是Spring Boot在Web應用中基于JdbcRealm安全驗證過程的詳細內(nèi)容,更多關于Spring Boot JdbcRealm安全驗證的資料請關注腳本之家其它相關文章!

相關文章

  • javaweb圖書商城設計之購物車模塊(3)

    javaweb圖書商城設計之購物車模塊(3)

    這篇文章主要為大家詳細介紹了javaweb圖書商城設計之購物車模塊的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-11-11
  • java中常用XML解析器的使用

    java中常用XML解析器的使用

    這篇文章主要介紹了java中常用XML解析器的使用的相關資料,需要的朋友可以參考下
    2023-02-02
  • Java生成中間logo的二維碼的示例代碼

    Java生成中間logo的二維碼的示例代碼

    這篇文章主要介紹了Java如何生成中間logo的二維碼,文中講解非常細致,代碼幫助大家更好的理解和學習,感興趣的朋友可以了解下
    2020-07-07
  • 分析ZooKeeper分布式鎖的實現(xiàn)

    分析ZooKeeper分布式鎖的實現(xiàn)

    在分布式的情況下,sychornized 和 Lock 已經(jīng)不能滿足我們的要求了,那么就需要使用第三方的鎖了,這里我們就使用 ZooKeeper 來實現(xiàn)一個分布式鎖
    2021-06-06
  • Java全排列算法字典序下的下一個排列講解

    Java全排列算法字典序下的下一個排列講解

    今天小編就為大家分享一篇關于Java全排列字典序下的下一個排列,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-02-02
  • java 如何判斷是否可以ping通某個地址

    java 如何判斷是否可以ping通某個地址

    這篇文章主要介紹了java 如何判斷是否可以ping通某個地址,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • Spring中@Transactional注解關鍵屬性和用法小結(jié)

    Spring中@Transactional注解關鍵屬性和用法小結(jié)

    在Spring框架中,@Transactional 是一個注解,用于聲明事務性的方法,它提供了一種聲明式的事務管理方式,避免了在代碼中直接編寫事務管理相關的代碼,本文給大家介紹@Transactional 注解的一些關鍵屬性和用法,感興趣的朋友一起看看吧
    2023-12-12
  • Java中的==使用方法詳解

    Java中的==使用方法詳解

    這篇文章主要給大家介紹了關于Java中的==使用方法的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-09-09
  • 解決SpringMvc中普通類注入Service為null的問題

    解決SpringMvc中普通類注入Service為null的問題

    這篇文章主要介紹了解決SpringMvc中普通類注入Service為null的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Spring Boot 2.4 對多環(huán)境配置的支持更改示例代碼

    Spring Boot 2.4 對多環(huán)境配置的支持更改示例代碼

    這篇文章主要介紹了Spring Boot 2.4 對多環(huán)境配置的支持更改,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-12-12

最新評論