<ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>


      寫在前面


      在一款應(yīng)用的整個(gè)生命周期,我們都會(huì)談及該應(yīng)用的數(shù)據(jù)安全問題。用戶的合法性與數(shù)據(jù)的可見性是數(shù)據(jù)安全中非常重要的一部分。但是,一方面,不同的應(yīng)用對于數(shù)據(jù)的合法性和可見性要求的維度與粒度都有所區(qū)別;另一方面,以當(dāng)前微服務(wù)、多服務(wù)的架構(gòu)方式,如何共享Session,如何緩存認(rèn)證和授權(quán)數(shù)據(jù)應(yīng)對高并發(fā)訪問都迫切需要我們解決。Shiro的出現(xiàn)讓我們可以快速和簡單的應(yīng)對我們應(yīng)用的數(shù)據(jù)安全問題

      Shiro介紹

      Shiro簡介

      這個(gè)官網(wǎng)解釋不抽象,所以直接用官網(wǎng)解釋:Apache Shiro?是一個(gè)強(qiáng)大且易用的 Java 安全框架,可以執(zhí)行身份驗(yàn)證、授權(quán)、加密和會(huì)話管理等。基于
      Shiro 的易于理解的API,您可以快速、輕松地使任何應(yīng)用程序變得安全(從最小的移動(dòng)應(yīng)用到最大的網(wǎng)絡(luò)和企業(yè)應(yīng)用)。

      談及安全,多數(shù) Java 開發(fā)人員都離不開 Spring 框架的支持,自然也就會(huì)先想到 Spring Security,那我們先來看二者的差別

      Shiro Spring Security
      簡單、靈活 復(fù)雜、笨重
      可脫離Spring 不可脫離Spring
      粒度較粗 粒度較細(xì)
      雖然 Spring Security 屬于名震中外 Spring 家族的一部分,但是了解 Shiro 之后,你不會(huì)想
      “嫁入豪門”,而是選擇追求「詩和遠(yuǎn)方」沖動(dòng)。

      橫看成嶺側(cè)成峰,遠(yuǎn)近高低各不同 (依舊是先了解概念就好)

      遠(yuǎn)看 Shiro 看輪廓



      Subject

      它是一個(gè)主體,代表了當(dāng)前“用戶”,這個(gè)用戶不一定是一個(gè)具體的人,與當(dāng)前應(yīng)用交互的任何東西都是Subject,如網(wǎng)絡(luò)爬蟲,機(jī)器人等;即一個(gè)抽象概念;所有
      Subject 都綁定到 SecurityManager,與 Subject 的所有交互都會(huì)委托給SecurityManager;可以把 Subject
      認(rèn)為是一個(gè)門面;SecurityManager 才是實(shí)際的執(zhí)行者

      SecurityManager

      安全管理器;即所有與安全有關(guān)的操作都會(huì)與 SecurityManager 交互;且它管理著所有 Subject;可以看出它是 Shiro
      的核心,它負(fù)責(zé)與后邊介紹的其他組件進(jìn)行交互,如果學(xué)習(xí)過 SpringMVC,你可以把它看成 DispatcherServlet前端控制器

      Realm

      域,Shiro 從 Realm 獲取安全數(shù)據(jù)(如用戶、角色、權(quán)限),就是說 SecurityManager 要驗(yàn)證用戶身份,那么它需要從 Realm
      獲取相應(yīng)的用戶進(jìn)行比較以確定用戶身份是否合法;也需要從 Realm 得到用戶相應(yīng)的角色/權(quán)限進(jìn)行驗(yàn)證用戶是否能進(jìn)行操作;可以把 Realm 看成
      DataSource,即安全數(shù)據(jù)源。

      近看 Shiro 看細(xì)節(jié)

      看圖瞬間懵逼?別慌,會(huì)為你拆解來看,結(jié)合著圖看下面的解釋,這不是啥大問題,且看:



      Subject

      主體,可以看到主體可以是任何可以與應(yīng)用交互的 “用戶”

      SecurityManager

      相當(dāng)于 SpringMVC 中的 DispatcherServlet;是 Shiro 的心臟;所有具體的交互都通過 SecurityManager
      進(jìn)行控制;它管理著所有 Subject、且負(fù)責(zé)進(jìn)行認(rèn)證和授權(quán)、及會(huì)話、緩存的管理

      Authenticator

      認(rèn)證器,負(fù)責(zé)主體認(rèn)證的,這是一個(gè)擴(kuò)展點(diǎn),如果用戶覺得 Shiro 默認(rèn)的不好,可以自定義實(shí)現(xiàn);需要自定義認(rèn)證策略(Authentication
      Strategy),即什么情況下算用戶認(rèn)證通過了

      Authrizer

      授權(quán)器,或者訪問控制器,用來決定主體是否有權(quán)限進(jìn)行相應(yīng)的操作;即控制著用戶能訪問應(yīng)用中的哪些功能

      Realm

      可以有 1 個(gè)或多個(gè)
      Realm,可以認(rèn)為是安全實(shí)體數(shù)據(jù)源,即用于獲取安全實(shí)體的;可以是JDBC實(shí)現(xiàn),也可以是LDAP實(shí)現(xiàn),或者內(nèi)存實(shí)現(xiàn)等等;由用戶提供;注意:Shiro
      不知道你的用戶/權(quán)限存儲(chǔ)在哪及以何種格式存儲(chǔ);所以我們一般在應(yīng)用中都需要實(shí)現(xiàn)自己的Realm

      SessionManager

      如果寫過 Servlet 就應(yīng)該知道 Session 的概念,Session 需要有人去管理它的生命周期,這個(gè)組件就是
      SessionManager;而Shiro 并不僅僅可以用在 Web 環(huán)境,也可以用在如普通的 JavaSE 環(huán)境、EJB等環(huán)境;所以,Shiro
      就抽象了一個(gè)自己的Session 來管理主體與應(yīng)用之間交互的數(shù)據(jù);這樣的話,比如我們在 Web 環(huán)境用,剛開始是一臺(tái)Web服務(wù)器;接著又上了臺(tái)EJB
      服務(wù)器;這時(shí)又想把兩臺(tái)服務(wù)器的會(huì)話數(shù)據(jù)放到一個(gè)地方,我們就可以實(shí)現(xiàn)自己的分布式會(huì)話(如把數(shù)據(jù)放到Memcached 服務(wù)器)

      SessionDAO

      DAO大家都用過,數(shù)據(jù)訪問對象,用于會(huì)話的 CRUD,比如我們想把 Session
      保存到數(shù)據(jù)庫,那么可以實(shí)現(xiàn)自己的SessionDAO,通過如JDBC寫到數(shù)據(jù)庫;比如想把 Session 放到 Memcached 中,可以實(shí)現(xiàn)自己的
      Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 進(jìn)行緩存,以提高性能;

      CacheManager

      緩存控制器,來管理如用戶、角色、權(quán)限等的緩存的;因?yàn)檫@些數(shù)據(jù)基本上很少去改變,放到緩存中后可以提高訪問的性能

      Cryptography

      密碼模塊,Shiro提高了一些常見的加密組件用于如密碼「加密/解密」的

      注意上圖的結(jié)構(gòu),我們會(huì)根據(jù)這張圖來逐步拆分講解,記住這張圖也更有助于我們理解 Shiro 的工作原理,所以依舊是打開兩個(gè)網(wǎng)頁一起看就好嘍

      搭建概覽

      多數(shù)小伙伴都在使用 Spring Boot, Shiro 也很應(yīng)景的定義了 starter,做了更好的封裝,對于我們來說使用起來也就更加方便,來看選型概覽

      序號 名稱 版本
      1 Springboot 2.0.4
      2 JPA 2.0.4
      3 Mysql 8.0.12
      4 Redis 2.0.4
      5 Lombok 1.16.22
      6 Guava 26.0-jre
      7 Shiro 1.4.0
      使用 Spring Boot,大多都是通過添加 starter 依賴,會(huì)自動(dòng)解決依賴包版本,所以自己嘗試的時(shí)候用最新版本不會(huì)有什么問題,比如 Shiro
      現(xiàn)在的版本是 1.5.0 了,整體問題不大,大家自行嘗試就好

      添加 Gradle 依賴管理



      大體目錄結(jié)構(gòu)



      application.yml 配置



      基本配置





      你就讓我看這?這只是一個(gè)概覽,先做到心中有數(shù),我們來看具體配置,逐步完成搭建

      其中 shiroFilter bean 部分指定了攔截路徑和相應(yīng)的過濾器,”/user/login”, ”/user”, ”/user/loginout”
      可以匿名訪問,其他路徑都需要授權(quán)訪問,shiro 提供和多個(gè)默認(rèn)的過濾器,我們可以用這些過濾器來配置控制指定url的權(quán)限(先了解個(gè)大概即可):

      配置縮寫 對應(yīng)的過濾器 功能
      anon AnonymousFilter 指定url可以匿名訪問
      authc FormAuthenticationFilter
      指定url需要form表單登錄,默認(rèn)會(huì)從請求中獲取username、password,rememberMe等參數(shù)并嘗試登錄,如果登錄不了就會(huì)跳轉(zhuǎn)到loginUrl配置的路徑。我們也可以用這個(gè)過濾器做默認(rèn)的登錄邏輯,但是一般都是我們自己在控制器寫登錄邏輯的,自己寫的話出錯(cuò)返回的信息都可以定制嘛。
      authcBasic BasicHttpAuthenticationFilter 指定url需要basic登錄
      Logout LogoutFilter 登出過濾器,配置指定url就可以實(shí)現(xiàn)退出功能,非常方便
      noSessionCreation NoSessionCreationFilter 禁止創(chuàng)建會(huì)話
      perms PermissionsAuthorizationFilter 需要指定權(quán)限才能訪問
      port PortFilter 需要指定端口才能訪問
      rest HttpMethodPermissionFilter
      將http請求方法轉(zhuǎn)化成相應(yīng)的動(dòng)詞來構(gòu)造一個(gè)權(quán)限字符串,這個(gè)感覺意義不大,有興趣自己看源碼的注釋
      roles RolesAuthorizationFilter 需要指定角色才能訪問
      ssl SslFilter 需要https請求才能訪問
      user UserFilter 需要已登錄或“記住我”的用戶才能訪問
      數(shù)據(jù)庫表設(shè)計(jì)

      數(shù)據(jù)庫表設(shè)計(jì)請參考 entity package下的 bean,通過@Entity 注解與 JPA 的設(shè)置自動(dòng)生成表結(jié)構(gòu) (你需要簡單的了解一下 JPA
      的功能)。

      我們要說重點(diǎn)啦~~~

      身份認(rèn)證

      身份認(rèn)證是一個(gè)證明 “李雷是李雷,韓梅梅是韓梅梅” 的過程,回看上圖,Realm 模塊就是用來做這件事的,Shiro 提供了
      IniRealm,JdbcReaml,LDAPReam等認(rèn)證方式,但自定義的 Realm 通常是最適合我們業(yè)務(wù)需要的,認(rèn)證通常是校驗(yàn)登錄用戶是否合法。

      新建用戶 User
      @Data @Entity public class User implements Serializable { @Id
      @GeneratedValue(strategy=GenerationType.AUTO) private Long id; @Column(unique
      =true) private String username; private String password; private String salt; }
      定義 Repository
      @Repository public interface UserRepository extends JpaRepository<User, Long>
      { public User findUserByUsername(String username); }
      編寫UserController:
      @GetMapping("/login") public void login(String username, String password) {
      UsernamePasswordToken token = new UsernamePasswordToken(username, password);
      token.setRememberMe(true); Subject currentUser = SecurityUtils.getSubject();
      currentUser.login(token); }
      自定義 Realm

      自定義 Realm,主要是為了重寫 doGetAuthenticationInfo(…)方法
      @Override protected AuthenticationInfo
      doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws
      AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken)
      authenticationToken; String username = token.getUsername(); User user =
      userRepository.findUserByUsername(username); SimpleAuthenticationInfo
      simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,
      user.getPassword(), getName());
      simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(user.getSalt()));
      return simpleAuthenticationInfo; }
      這些代碼我需要做一個(gè)說明,你可能也滿肚子疑惑:

      * 這段代碼怎么應(yīng)用了 shiro?
      * controller 是怎么調(diào)用到 custom realm 的?
      * 重寫的 doGetAuthenticationInfo(…) 方法目的是什么?
      認(rèn)證流程說明

      用戶訪問/user/login 路徑,生成 UsernamePasswordToken,
      通過SecurityUtils.getSubject()獲取Subject(currentUser),調(diào)用 login
      方法進(jìn)行驗(yàn)證,讓我們跟蹤一下代碼,瞧一瞧就知道自定義的CustomRealm怎樣起作用的,一起來看源碼:



      到這里我們要停一停了,請回看 Shiro 近景圖,將源碼追蹤路徑與其對比,是完全一致的

      授權(quán)

      身份認(rèn)證是驗(yàn)證你是誰的問題,而授權(quán)是你能干什么的問題,

      產(chǎn)品經(jīng)理:申購模塊只能科室看
      程序員:好的
      產(chǎn)品經(jīng)理:科長權(quán)限大一些,他也能看申購模塊
      程序員:好的(黑臉)
      產(chǎn)品經(jīng)理:科長不但能看,還能修改數(shù)據(jù)
      程序員:關(guān)公提大刀,拿命來


      作為程序員,我們的宗旨是:「能動(dòng)手就不吵吵」; 硝煙怒火拔地起,耳邊響起駝鈴聲(Shiro):「放下屠刀,立地成佛」授權(quán)沒有那么麻煩,大家好商量…

      整個(gè)過程和身份認(rèn)證基本是一毛一樣,你對比看看

      角色實(shí)體創(chuàng)建

      涉及到授權(quán),自然要和角色相關(guān),所以我們創(chuàng)建 Role 實(shí)體:
      @Data @Entity public class Role { @Id
      @GeneratedValue(strategy=GenerationType.AUTO) private Long id; @Column(unique
      =true) private String roleCode; private String roleName; }
      新建 Role Repository
      @Repository public interface RoleRepository extends JpaRepository<Role, Long>
      { @Query(value = "select roleId from UserRoleRel ur where ur.userId = ?1")
      List<Long> findUserRole(Long userId); List<Role> findByIdIn(List<Long> ids); }
      定義權(quán)限實(shí)體 Permission
      @Data @Entity public class Permission { @Id
      @GeneratedValue(strategy=GenerationType.AUTO) private Long id; @Column(unique
      =true) private String permCode; private String permName; }
      定義 Permission Repository
      @Repository public interface PermissionRepository extends
      JpaRepository<Permission, Long> { @Query(value = "select permId from
      RolePermRel pr where pr.roleId in ?1") List<Long> findRolePerm(List<Long>
      roleIds); List<Permission> findByIdIn(List<Long> ids); }
      建立用戶與角色關(guān)系

      其實(shí)可以通過 JPA 注解來制定關(guān)系的,這里為了說明問題,以單獨(dú)外鍵形式說明
      @Data @Entity public class UserRoleRel { @Id
      @GeneratedValue(strategy=GenerationType.AUTO) private Long id; private Long
      userId; private Long roleId; }
      建立角色與權(quán)限關(guān)系
      @Data @Entity public class RolePermRel { @Id
      @GeneratedValue(strategy=GenerationType.AUTO) private Long id; private Long
      permId; private Long roleId; }
      編寫 UserController
      @RequiresPermissions("user:list:view") @GetMapping() public void
      getAllUsers(){ List<User> users = userRepository.findAll(); }
      @RequiresPermissions("user:list:view")
      注解說明具有用戶:列表:查看權(quán)限的才可以訪問),官網(wǎng)明確給出權(quán)限定義格式,包括通配符等,我希望你自行去查看

      自定義 CustomRealm (主要重寫 doGetAuthorizationInfo) 方法:



      與認(rèn)證流程如出一轍,只不過多了用戶,角色,權(quán)限的關(guān)系罷了

      授權(quán)流程說明

      這里通過過濾器(見Shiro配置)和注解二者結(jié)合的方式來進(jìn)行授權(quán),和認(rèn)證流程一樣,最終會(huì)走到我們自定義的 CustomRealm 中,同樣 Shiro
      默認(rèn)提供了許多注解用來處理不同的授權(quán)情況

      注解 功能
      @RequiresGuest 只有游客可以訪問
      @RequiresAuthentication 需要登錄才能訪問
      @RequiresUser 已登錄的用戶或“記住我”的用戶能訪問
      @RequiresRoles 已登錄的用戶需具有指定的角色才能訪問
      @RequiresPermissions 已登錄的用戶需具有指定的權(quán)限才能訪問(如果不想和產(chǎn)品經(jīng)理華山論劍,推薦用這個(gè)注解)
      授權(quán)官網(wǎng)給出明確的授權(quán)策略與案例,請查看:http://shiro.apache.org/permissions.html

      上面的例子我們通過一直在通過訪問 Mysql 獲取用戶認(rèn)證和授權(quán)信息,這中方式明顯不符合生產(chǎn)環(huán)境的需求

      Session會(huì)話管理

      做過 Web 開發(fā)的同學(xué)都知道 Session 的概念,最常用的是 Session 過期時(shí)間,數(shù)據(jù)在 Session 的 CRUD,同樣看上圖,我們需要關(guān)注
      SessionManager 和 SessionDAO 模塊,Shiro starter 已經(jīng)提供了基本的
      Session配置信息,我們按需在YAML中配置就好(官網(wǎng)https://shiro.apache.org/spring-boot.html
      已經(jīng)明確給出Session的配置信息)

      Key Default Value Description
      shiro.enabled true Enables Shiro’s Spring module
      shiro.web.enabled true Enables Shiro’s Spring web module
      shiro.annotations.enabled true Enables Spring support for Shiro’s annotations
      shiro.sessionManager.deleteInvalidSessions true Remove invalid session from
      session storage
      shiro.sessionManager.sessionIdCookieEnabled true Enable session ID to cookie,
      for session tracking
      shiro.sessionManager.sessionIdUrlRewritingEnabled true Enable session URL
      rewriting support
      shiro.userNativeSessionManager false If enabled Shiro will manage the HTTP
      sessions instead of the container
      shiro.sessionManager.cookie.name JSESSIONID Session cookie name
      shiro.sessionManager.cookie.maxAge -1 Session cookie max age
      shiro.sessionManager.cookie.domain null Session cookie domain
      shiro.sessionManager.cookie.path null Session cookie path
      shiro.sessionManager.cookie.secure false Session cookie secure flag
      shiro.rememberMeManager.cookie.name rememberMe RememberMe cookie name
      shiro.rememberMeManager.cookie.maxAge one year RememberMe cookie max age
      shiro.rememberMeManager.cookie.domain null RememberMe cookie domain
      shiro.rememberMeManager.cookie.path null RememberMe cookie path
      shiro.rememberMeManager.cookie.secure false RememberMe cookie secure flag
      shiro.loginUrl /login.jsp Login URL used when unauthenticated users are
      redirected to login page
      shiro.successUrl / Default landing page after a user logs in (if alternative
      cannot be found in the current session)
      shiro.unauthorizedUrl null Page to redirect user to if they are unauthorized
      (403 page)

      分布式服務(wù)中,我們通常需要將Session信息放入Redis中來管理,來應(yīng)對高并發(fā)的訪問需求,這時(shí)只需重寫SessionDAO即可完成自定義的Session管理

      整合Redis
      @Configuration public class RedisConfig { @Autowired private
      RedisConnectionFactory redisConnectionFactory; @Bean public
      RedisTemplate<String, Object> stringObjectRedisTemplate() {
      RedisTemplate<String, Object> template = new RedisTemplate<>();
      template.setConnectionFactory(redisConnectionFactory);
      template.setKeySerializer(new StringRedisSerializer());
      template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return
      template; } }
      重寫SessionDao




      查看源碼,可以看到調(diào)用默認(rèn)SessionManager的retriveSession方法,我們重寫該方法,將Session放入HttpRequest中,進(jìn)一步提高session訪問效率



      向ShiroConfig中添加配置

      其實(shí)在概覽模塊已經(jīng)給出代碼展示,這里單獨(dú)列出來做說明:
      /** * 自定義RedisSessionDao用來管理Session在Redis中的CRUD * @return */ @Bean(name =
      "redisSessionDao") public RedisSessionDao redisSessionDao(){ return new
      RedisSessionDao(); } /** * 自定義SessionManager,應(yīng)用自定義SessionDao * @return */
      @Bean(name = "customerSessionManager") public CustomerWebSessionManager
      customerWebSessionManager(){ CustomerWebSessionManager
      customerWebSessionManager = new CustomerWebSessionManager();
      customerWebSessionManager.setSessionDAO(redisSessionDao()); return
      customerWebSessionManager; } /** * 定義Security manager * @param customRealm *
      @return */ @Bean(name = "securityManager") public DefaultWebSecurityManager
      defaultWebSecurityManager(CustomRealm customRealm) { DefaultWebSecurityManager
      securityManager = new DefaultWebSecurityManager ();
      securityManager.setRealm(customRealm);
      securityManager.setSessionManager(customerWebSessionManager()); //
      可不指定,Shiro會(huì)用默認(rèn)Session manager
      securityManager.setCacheManager(redisCacheManagers());
      //可不指定,Shiro會(huì)用默認(rèn)CacheManager //
      securityManager.setSessionManager(defaultWebSessionManager()); return
      securityManager; } /** * 定義session管理器 * @return */ @Bean(name =
      "sessionManager") public DefaultWebSessionManager defaultWebSessionManager(){
      DefaultWebSessionManager defaultWebSessionManager = new
      DefaultWebSessionManager();
      defaultWebSessionManager.setSessionDAO(redisSessionDao()); return
      defaultWebSessionManager; }
      至此,將 session 信息由 redis 管理功能就這樣完成了

      緩存管理

      應(yīng)對分布式服務(wù),對于高并發(fā)訪問數(shù)據(jù)庫權(quán)限內(nèi)容是非常低效的方式,同樣我們可以利用Redis來解決這一問題,將授權(quán)數(shù)據(jù)緩存到Redis中

      新建 RedisCache
      @Slf4j @Component public class RedisCache<K, V> implements Cache<K, V> {
      public static final String SHIRO_PREFIX = "shiro-cache:"; @Resource private
      RedisTemplate<String, Object> stringObjectRedisTemplate; private String
      getKey(K key){ if (key instanceof String){ return (SHIRO_PREFIX + key); }
      return key.toString(); } @Override public V get(K k) throws CacheException {
      log.info("read from redis..."); V v = (V)
      stringObjectRedisTemplate.opsForValue().get(getKey(k)); if (v != null){ return
      v; } return null; } @Override public V put(K k, V v) throws CacheException {
      stringObjectRedisTemplate.opsForValue().set(getKey(k), v);
      stringObjectRedisTemplate.expire(getKey(k), 100, TimeUnit.SECONDS); return v; }
      @Override public V remove(K k) throws CacheException { V v = (V)
      stringObjectRedisTemplate.opsForValue().get(getKey(k));
      stringObjectRedisTemplate.delete((String) get(k)); if (v != null){ return v; }
      return null; } @Override public void clear() throws CacheException {
      //不要重寫,如果只保存shiro數(shù)據(jù)無所謂 } @Override public int size() { return 0; } @Override
      public Set<K> keys() { return null; } @Override public Collection<V> values() {
      return null; } }
      新建 RedisCacheManager
      public class RedisCacheManager implements CacheManager { @Resource private
      RedisCache redisCache; @Override public <K, V> Cache<K, V> getCache(String s)
      throws CacheException { return redisCache; } }
      至此,我們不用每次訪問 Mysql DB 來獲取認(rèn)證和授權(quán)信息,而是通過 Redis 來緩存這些信息,大大提升了效率,也滿足分布式系統(tǒng)的設(shè)計(jì)需求

      總結(jié)

      回復(fù)公眾號 「demo」獲取 demo
      代碼。這里只是梳理了Springboot整合Shiro的流程,以及應(yīng)用Redis最大化利用Shiro,Shiro的使用細(xì)節(jié)還很多,官網(wǎng)說的也很明確,帶著上面的架構(gòu)圖來理解Shiro會(huì)事半功倍,感覺這里面的代碼挺多挺頭大的?那是你沒有自己動(dòng)手去嘗試,結(jié)合官網(wǎng)與
      demo 相信你會(huì)對 Shiro 有更好的理解,另外你可以理解 Shiro 是 mini 版本的 Spring
      Security,我希望以小見大,當(dāng)需要更細(xì)粒度的認(rèn)證授權(quán)時(shí),也會(huì)對理解 Spring Security 有很大幫助,點(diǎn)擊文末「閱讀原文」,效果更好

      落霞與孤鶩齊飛 秋水共長天一色,產(chǎn)品經(jīng)理和程序員一片祥和…

      靈魂追問

      * 都說 Redis 是單線程,但是很快,你知道為什么嗎?
      * 你們項(xiàng)目中是怎樣控制認(rèn)證授權(quán)的呢?當(dāng)授權(quán)有變化,對于程序員來說,這個(gè)修改是災(zāi)難嗎?
      提高效率工具



      MarkDown 表格生成器

      本文的好多表格是從官網(wǎng)粘貼的,如何將其直接轉(zhuǎn)換成 MD table 呢?那么
      https://www.tablesgenerator.com/markdown_tables 就可以幫到你了,無論是生成 MD table,還是粘貼內(nèi)容生成
      table 和內(nèi)容都是極好的,當(dāng)然了不止 MD table,自己發(fā)現(xiàn)吧,更多工具,公眾號回復(fù) 「工具」獲得



      推薦閱讀

      * 只會(huì)用 git pull ?有時(shí)候你可以嘗試更優(yōu)雅的處理方式
      <https://mp.weixin.qq.com/s/6dg3u2PkcTSQHu_3T_QYnA>
      * 雙親委派模型:大廠高頻面試題,輕松搞定 <https://mp.weixin.qq.com/s/Dnr1jLebvBUHnziZzSfcrA>
      * 面試還不知道BeanFactory和ApplicationContext的區(qū)別?
      <https://mp.weixin.qq.com/s/YBQB086ADBjHUmwrFQrWew>
      * 如何設(shè)計(jì)好的RESTful API <https://mp.weixin.qq.com/s/hR1TqkVzwZ_T8fuMnsM4hQ>
      * 程序猿為什么要看源碼? <https://mp.weixin.qq.com/s/V7h8O6pVFQ-nr_iA2SNqtw>
      歡迎持續(xù)關(guān)注公眾號:「日拱一兵」

      * 前沿 Java 技術(shù)干貨分享
      * 高效工具匯總 回復(fù)「工具」
      * 面試問題分析與解答
      * 技術(shù)資料領(lǐng)取 回復(fù)「資料」
      以讀偵探小說思維輕松趣味學(xué)習(xí) Java 技術(shù)棧相關(guān)知識,本著將復(fù)雜問題簡單化,抽象問題具體化和圖形化原則逐步分解技術(shù)問題,技術(shù)持續(xù)更新,請持續(xù)關(guān)注......


      友情鏈接
      ioDraw流程圖
      API參考文檔
      OK工具箱
      云服務(wù)器優(yōu)惠
      阿里云優(yōu)惠券
      騰訊云優(yōu)惠券
      京東云優(yōu)惠券
      站點(diǎn)信息
      問題反饋
      郵箱:[email protected]
      QQ群:637538335
      關(guān)注微信

        <ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>
          久热一本 | 无遮挡打光屁屁打屁股 | 国产精品一二三产区m553小说 | 污网站免费在线观看 | 狠狠骚 | 久久精品伦理 | 欧美A片在线观看 | TS国产CHINA人妖 | 一区二区三区无码播放 | 老牛影视一区二区 |