【登錄及鑑權】-盤點那些主流的開源登錄及權限認證框架 (下)

前言:接上篇,上篇主要講理論,下篇講實戰,結合代碼演示SpringSecurity,Shiro,Oauth,jwt token以及單點登錄等當下主流的登錄及權限管理.在技術上我是個喜新厭舊的渣男,全篇以截至2020年2月最新的Springboot及其它包版本爲例演示.


完整的項目我已上傳至GitHub,如有需要可以下載下來參考,地址:https://github.com/laohanjianshen/login-auth


 

1.SpringSecurity

新建一個Springboot工程,並引入Springsecurity依賴(爲了不浪費篇幅,Sringboot web jpa jdbc等包請自行添加)

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

新建一個Controller類,並啓動你的Springboot項目,不出意外訪問任何URL時你將會看到:

說明SpringSecurity已經生效,下面正式進入研發階段...

項目整體包及結構如下圖所示:

雖然內容乍看上去有點多,而且比網上大多數教程複雜一點,其實並不多,只需要三個步驟即可實現,之所以多是因爲我用了更規範的寫法,更接近生產環境.

第一步:根據RBAC,我們首先需要創建User,Role,Permission這三個對象及其DAO層:

@Entity
@Data
public class User implements UserDetails, Serializable {
    @Id @GeneratedValue
    private long uid;//主鍵.

    private String username;//用戶名.

    private String password;//密碼.

    //省略用戶的其它信息,如手機號,郵箱等...

    //用戶 - 角色關係. 多對多./
    @ManyToMany(fetch= FetchType.EAGER)//立即從數據庫中獲取.
    @JoinTable(name="user_role",joinColumns= {@JoinColumn(name="uid")},inverseJoinColumns= {@JoinColumn(name="role_id")})
    private List<Role> roles;

    //當前用戶的角色列表
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roles;
    }
    //賬號是否未過期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    //賬號是否未被鎖定
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    //證書是否過期
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    //是否可用
    @Override
    public boolean isEnabled() {
        return true;
    }
}
@Entity
@Data
public class Role implements GrantedAuthority, Serializable {
    @Id
    @GeneratedValue
    private long rid;

    private String name;//角色名.

    private String descprtion;//角色描述.

    @Override
    public String getAuthority() {
        return name;
    }
}
@Data
@Entity
public class Permission implements Serializable {
    @Id @GeneratedValue
    private long id;

    private String name;//權限名稱.
    private String description;//描述.
    private String url;//地址.
    private long pid;//父id.

    //角色和權限的關係  多對多.
    @ManyToMany(fetch= FetchType.EAGER)
    @JoinTable(name="role_permission",joinColumns= {@JoinColumn(name="permission_id")},
            inverseJoinColumns= {@JoinColumn(name="role_id")})
    private List<Role> roles;
}

配置中加入JPA自動生成表策略 :

spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update

然後運行項目,標準的5張表(還有一張是id序列表)就被創建好了,因爲是Demo,所以我選擇用JPA作爲ORM框架,增刪改查等都比較方便.

然後添加兩個用戶,普通用戶和管理員並分別初始化權限等信息:

INSERT INTO `permission` VALUES (1, '公共頁面訪問權限', 'common', 0, '/user/common');
INSERT INTO `permission` VALUES (2, '管理員頁面訪問權限', 'admin', 0, '/user/admin');

INSERT INTO `role` VALUES (1, '普通用戶', 'ordinary');
INSERT INTO `role` VALUES (2, '老闆', 'boss');

INSERT INTO `role_permission` VALUES (1, 1);
INSERT INTO `role_permission` VALUES (1, 2);
INSERT INTO `role_permission` VALUES (2, 2);

INSERT INTO `user` VALUES (1, 'e10adc3949ba59abbe56e057f20f883e', 'user');
INSERT INTO `user` VALUES (2, 'e10adc3949ba59abbe56e057f20f883e', 'admin');

INSERT INTO `user_role` VALUES (1, 1);
INSERT INTO `user_role` VALUES (2, 1);
INSERT INTO `user_role` VALUES (2, 2);

相關SQL我已放入項目中,可以直接用Navicat等工具導入也行. 

DAO層請自行創建,比較簡單,就三個接口,這裏省略了,至此第一步就已完成.

第二步:創建登錄及鑑權的Service層(劃重點,這塊是整個Spring-security的核心)

①在上篇中我有提到,SpringSecurity用到的跟用戶相關的信息來源於UserDetailService,所以我們需要實現此接口

@Service
public class MyUserDetailService implements UserDetailsService {
    @Resource
    UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        return userRepository.findByUsername(s);
    }
}

②自定義過濾器的元數據 ,這步的核心是getAttributes(Object o)方法,該方法需要返回當前請求所需要的用戶身份列表(roleNames).

@Service
public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource {
    @Resource
    private PermissionRepository permissionRepository;
    /**
     * 每一個資源所需要的角色 ,Collection<ConfigAttribute>決策器會用到,用Map作緩存,避免每次請求都去查庫
     */
    private static HashMap<String, Collection<ConfigAttribute>> map = null;

    /**
     * 獲取決策器DecisionManager所需要的當前請求對應的role
     * @param o
     * @return
     * @throws IllegalArgumentException
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        if (null == map) {
            loadResourceDefine();
        }
        HttpServletRequest request = ((FilterInvocation) o).getHttpRequest();

        for (Iterator<String> it = map.keySet().iterator(); it.hasNext(); ) {
            String url = it.next();
            if (new AntPathRequestMatcher(url).matches(request)) {
                //這裏返回的就是當前請求的url所需要的roleNameList
                return map.get(url);
            }
        }
        return null;
    }
    
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }
    
    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }

    /**
     * 將permission表中的url對應的權限通過role_permission表與role關聯並存入map
     */
    private void loadResourceDefine() {
        map = new HashMap<>(16);
        List<Permission> permissions = permissionRepository.findAll();
        for (Permission permission : permissions) {
            String url = permission.getUrl();
            StringBuilder sb = new StringBuilder();
            permission.getRoles().forEach(r->{
                sb.append(r.getName());
            });
            String name = sb.toString();
            ConfigAttribute configAttribute = new SecurityConfig(name);
            if (map.containsKey(url)) {
                map.get(url).add(configAttribute);
            } else {
                List<ConfigAttribute> list = new ArrayList<>();
                list.add(configAttribute);
                map.put(url, list);
            }
        }
    }
}

 ③覆蓋SpringSecurity的攔截器,用上面自定義的元數據:

@Component
public class MyFilterSecurityInterceptor extends FilterSecurityInterceptor {
    @Autowired
    private FilterInvocationSecurityMetadataSource securityMetadataSource;

    @Autowired
    public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
        super.setAccessDecisionManager(myAccessDecisionManager);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        invoke(fi);
    }

    public void invoke(FilterInvocation fi) {
        InterceptorStatusToken token = super.beforeInvocation(fi);
        try {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ServletException e) {
            e.printStackTrace();
        } finally {
            super.afterInvocation(token, null);
        }
    }

    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.securityMetadataSource;
    }
}

④ 自定義決策器,核心方法是decide(...),此方法用來判斷當前登錄用戶是否有權限訪問該資源.

@Component
public class MyAccessDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
        if (CollectionUtils.isEmpty(collection)) {
            return;
        } else {
            String needRole;
            for (Iterator<ConfigAttribute> iter = collection.iterator(); iter.hasNext(); ) {
                needRole = iter.next().getAttribute();
                for (GrantedAuthority ga : authentication.getAuthorities()) {
                    if (needRole.contains(ga.getAuthority())) {
                        //當前請求所需角色列表包含當前登陸人的角色,允許訪問
                        return;
                    }
                }
            }
            throw new AccessDeniedException("當前訪問沒有權限");
        }
    }
    /**
     * 表示此AccessDecisionManager是否能夠處理傳遞的ConfigAttribute呈現的授權請求
     */
    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }
    /**
     * 表示當前AccessDecisionManager實現是否能夠爲指定的安全對象(方法調用或Web請求)提供訪問控制決策
     */
    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

此方法中的:

authentication包含了當前用戶的相關信息

Object o其實就是FilterInvocation對象,可以通過它來獲取HttpServletRequest等...所以如果要簡寫的話可以把②③中的內容挪到此處,但不推薦,不規範,雖然可以減少代碼.

collection其實就是②中的請求url對應的roleNameList.

至此,步驟二完成.

第三步:全局配置

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private MyUserDetailService userDetailService;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

        //校驗用戶
        auth.userDetailsService(userDetailService).passwordEncoder(new PasswordEncoder() {
            //對密碼進行加密
            @Override
            public String encode(CharSequence charSequence) {
                return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
            }

            //對密碼進行判斷匹配
            @Override
            public boolean matches(CharSequence charSequence, String s) {
                String encode = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
                boolean res = s.equals(encode);
                return res;
            }
        });
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/", "index", "/login", "/login-error", "/401", "/css/**", "/js/**").permitAll()//默認放行這些資源
                .anyRequest().authenticated()//其餘請求統統要走spring-security的攔截
                .and()
                .formLogin().loginPage("/login").failureUrl("/login-error")//登錄失敗
                .and()
                .exceptionHandling().accessDeniedPage("/401");//權限異常時的跳轉頁面
        http.logout().logoutSuccessUrl("/");
    }
}

configureGlobal中主要配置我們自定義的UserDetailService,以及對密碼的加密解密,可以看到,Spring-security對加密解密的支持非常友好,不需要你再去花大量筆墨去寫工具類.

configure方法主要配置一些攔截和跳轉信息

幾個靜態頁面我就不貼了,太浪費篇幅,有需要可以去Git拉取

現在我們可以測試一下:

@Controller
public class SecurityController {

    @RequestMapping("/")
    public String root() {
        return "redirect:/index";
    }

    @RequestMapping("/index")
    public String index() {
        return "index";
    }

    @RequestMapping("/login")
    public String login() {
        return "login";
    }

    @RequestMapping("/login-error")
    public String loginError(Model model) {
        model.addAttribute("loginError", true);
        return "login";
    }

    @GetMapping("/401")
    public String accessDenied() {
        return "401";
    }

    @GetMapping("/user/common")
    public String common() {
        return "user/common";
    }

    @GetMapping("/user/admin")
    public String admin() {
        return "user/admin";
    }

}

啓動項目後測試符合預期:登錄user普通用戶賬號,訪問公共頁面被允許,受保護頁面被拒絕.登錄admin用戶則可不受限制.

 

至此,Spring-security的部分先告一段落.


2.Apache Shiro

shiro的配置和使用都比較簡單,爲了演示更簡單,我這裏省略從數據庫查詢的操作,用Map來模擬.

新建一個Springboot的子工程,先來看一下整體的結構:

第一步,引入shiro依賴,其它web,jpa等相關依賴請自行引入,完整代碼可以從本篇開頭那裏的Git倉拉取.

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

第二步,創建User等類對象,因爲shiro也是RBAC的,比較簡單,我就不貼了.

第三步,創建自定義Reaml繼承自AuthorizingRealm,並覆寫doGetAuthorizationInfo方法和doGetAuthorticationInfo方法

public class CustomRealm extends AuthorizingRealm {
    /**
     * 處理授權
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String name = principalCollection.getPrimaryPrincipal().toString();
        User user = getUserByName(name);
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        user.getRoles().forEach(role -> {
            //添加角色
            authorizationInfo.addRole(role.getRoleName());
            //添加權限
            role.getPermission().forEach(permission -> {
                authorizationInfo.addStringPermission(permission.getPermissionName());
            });
        });
        return authorizationInfo;
    }

    /**
     * 處理認證
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String name = authenticationToken.getPrincipal().toString();
        User user = getUserByName(name);
        return new SimpleAuthenticationInfo(name, user.getPassword(), getName());
    }

    private User getUserByName(String name) {
        //模擬數據庫查詢
        Permission permission1 = new Permission(1L, "common");
        Permission permission2 = new Permission(2L, "private");
        Set<Permission> permissionSet1 = new HashSet<>();
        permissionSet1.add(permission1);
        Set<Permission> permissionSet2 = new HashSet<>();
        permissionSet2.add(permission1);
        permissionSet2.add(permission2);
        Role role1 = new Role(1L, "ordinary", permissionSet1);
        Role role2 = new Role(2L, "admin", permissionSet2);
        Set<Role> roleSet1 = new HashSet<>();
        roleSet1.add(role1);
        Set<Role> roleSet2 = new HashSet<>();
        roleSet2.add(role1);
        roleSet2.add(role2);
        User user1 = new User(1L, "user", "123456", "abc", roleSet1);
        User user2 = new User(2L, "admin", "123456", "def", roleSet2);
        Map<String, User> map = new HashMap<>(3);
        map.put(user1.getUsername(), user1);
        map.put(user2.getUsername(), user2);
        return map.get(name);
    }
}

其中doGetAuthorizationInfo方法負責封裝權限信息,doGetAuthorticationInfo負責封裝認證(賬戶名,密碼等)信息

第四步,全局配置

@Configuration
public class ShiroConfig {

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String, String> map = new HashMap<>();
        //登出
        map.put("/logout", "logout");
        //對所有用戶認證
        map.put("/**", "authc");
        //登錄
        shiroFilterFactoryBean.setLoginUrl("/login");
        //首頁
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //錯誤頁面,認證不通過跳轉
        shiroFilterFactoryBean.setUnauthorizedUrl("/error");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    //配置自定義的Realm
    @Bean
    public CustomRealm customRealm(){
        return new CustomRealm();
    }
    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(customRealm());
        return securityManager;
    }
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    //解決spring aop的二次代理問題
    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
        defaultAAP.setProxyTargetClass(true);
        return defaultAAP;
    }

}

配置類主要配置ShiroFilterFactoryBean,和自定義的Realm.

ShiroFilterFactoryBean負責配置默認的登錄登出以及首頁,錯誤頁面等信息.

自定義的Realm一定要設置給SecurityManager來處理,否則不生效.

編寫測試類

​
@Controller
public class LoginController {

    @RequestMapping("/login")
    @ResponseBody
    public String login(User user) {
        //添加用戶認證信息
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
                user.getUsername(),
                user.getPassword()
        );
        try {
            //進行驗證,這裏可以捕獲異常,然後返回對應信息
            subject.login(usernamePasswordToken);
        } catch (AuthenticationException e) {
            e.printStackTrace();
            return "賬號或密碼錯誤!";
        } catch (AuthorizationException e) {
            e.printStackTrace();
            return "沒有權限";
        }
        return "login success";
    }

    //註解驗角色和權限
    @RequiresRoles("ordinary")
    @RequiresPermissions("common")
    @RequestMapping("/index")
    @ResponseBody
    public String index() {
        return "index!";
    }

    @RequiresRoles("admin")
    @RequiresPermissions("private")
    @RequestMapping("/limit")
    @ResponseBody
    public String limit() {
        return "limit!";
    }
}

​

可以啓動項目後在瀏覽器輸入:

http://localhost:8080/login?username=user&password=123456 

登錄普通用戶然後分別訪問index和limit接口,然後再登錄admin賬號,重複此流程並觀察,我已經測過了,結果符合預期.

至此就完成了整個shiro的演示,可以看出shiro在配置上要比springsecurity簡單很多,在springboot誕生前,相比之下的簡單程度更是不言而喻,但基礎功能上兩者不相上下,所以在早期項目中喜歡用shrio的開發者更多一些,現在這種局勢已經被逆轉,現在的主角是spring-security,所以我不想再浪費篇幅在shiro上.


在更多的場景裏,單點登錄和oauth纔是我們想要的.

關於Oauth如果想學習的極力推薦阮一峯老師的教程,真的太讚了:http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html

在上篇中我已經介紹了單點登錄和oauth的好處,這裏不再贅述,在微服務架構流行的今天,大部分上點規模的企業都會有自己的認證中心,也就是把傳統的登錄鑑權模塊單獨抽取出來,做成一個獨立的認證服務,該企業下的子應用可以直接去請求該服務,完成登錄和鑑權,具體的流程可以參照下圖(引自李衛民老師https://www.funtl.com/zh/spring-security-oauth2):

其中,客戶端就是我們具體的某個應用,甚至是瀏覽器,認證服務器就是本篇重點要講的負責登錄和鑑權的服務,資源服務器則是一些受保護的資源,也就是登錄後且具備某些權限纔可以訪問的資源.

先來看一下認證服務器的項目結構:

項目下載地址:https://github.com/laohanjianshen/spring-security-oauth2

大部分都是RBAC相關的內容,與前面講的無異,核心配置其實只有AuthorizationServerConfiguration和WebSecurityConfiguration.

AuthorizationServerConfiguration繼承並覆蓋AuthorizationServerConfigurerAdapter類中的configure方法,以此來告訴SpringSecurity,當前認證服務器要使用tokenstore來存放token,客戶端採用Jdbc方式.

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource());
    }

    @Bean
    public ClientDetailsService jdbcClientDetailsService() {
        return new JdbcClientDetailsService(dataSource());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore());
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 配置客戶端
        clients.withClientDetails(jdbcClientDetailsService());
    }

}

 WebSecurityConfiguration繼承並覆寫WebSecurityConfigurerAdapter類中的configure方法,以此來告訴SpringSecurity默認的登錄及鑑權Servierce是UserDetailService,至於UserDetailService,是我們自己來實現的.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                .antMatchers("/oauth/check_token");
    }
}

UserDetailServiceImpl類實現SpringSecurity定義的UserDetailsService接口,覆寫loadUserByUsername方法,通過用戶名從數據庫中查詢並封裝該用戶的賬號,密碼,權限等信息. 

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private TbUserService tbUserService;
    @Autowired
    private TbPermissionService tbPermissionService;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        TbUser tbUser = tbUserService.getUserByName(s);
        List<GrantedAuthority> grantedAuthorities = Lists.newArrayList();
        if (Objects.nonNull(tbUser)) {
            List<TbPermission> permissions = tbPermissionService.getPermissionListByUserId(tbUser.getId());
            permissions.forEach(tbPermission -> {
                GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(tbPermission.getEnname());
                grantedAuthorities.add(grantedAuthority);
            });
        }
        return new User(tbUser.getUsername(), tbUser.getPassword(), grantedAuthorities);
    }
}

封裝好這些之後,具體的登錄和權限判定,SpringSecurity框架會幫我們去實現具體的過程,我們無需再操心後面的實現過程.

然後在資源服務器的配置文件中指定對應的認證服務器地址,就可以將認證服務器和資源服務器的聯繫建立起來.

security:
  oauth2:
    client:
      client-id: client
      client-secret: secret
      access-token-uri: http://localhost:8080/oauth/token
      user-authorization-uri: http://localhost:8080/oauth/authorize
    resource:
      token-info-uri: http://localhost:8080/oauth/check_token

然後分別啓動客戶端和服務端,然後進行測試:

首先直接訪問資源服務器,這時候系統提示我沒有登錄或沒有權限

然後訪問認證服務器進行授權:

授權完成後,會跳轉到一個backUrl,並攜帶一個code,通過此code我們可以申請到訪問資源服務器的token令牌

通過此code+clientId+client secret即可獲取到token

然後我們在訪問資源服務器時,攜帶該token就可以正確訪問資源了:

上面爲了演示和幫助理解,把部分步驟拆分開來了,在實際業務中,過程更爲簡化,完整的過程是:

①用戶請求資源服務器->②如果用戶未登錄或未授權->③跳轉至授權頁面->④授權成功後頒發令牌並攜帶該令牌跳轉至資源服務器->⑤資源服務器請求認證服務器判定該令牌是否有效->⑥有效即放行讓用戶訪問資源.

①~⑥中用戶可見的步驟只有①③④⑥,其它步驟都由後臺自動完成.


登錄和鑑權幾乎是每個系統必備的,但在實際開發中接觸的卻比較少,因爲大部分公司都有現成的輪子,所以關於登錄鑑權這塊平時開發的極少,所以特意拎出來再複習一遍.

最後特別感謝阮一峯老師和李衛民老師,能給予一些學習和參考的資料,收穫頗多.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章