Showing posts with label spring security. Show all posts
Showing posts with label spring security. Show all posts

Saturday, May 9, 2020

透過 OAuth2.0 使用 Facebook 登入 範例

去 facebook develop 開發
新增 facebook login

pom.xml

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

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

        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>bootstrap</artifactId>
            <version>3.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>webjars-locator-core</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
            <version>2.1.5.RELEASE</version>
        </dependency>
    </dependencies>

appliceation.yml

security:
  oauth2:
    client:
      clientId: ===
      clientSecret: ====
      accessTokenUri: https://graph.facebook.com/oauth/access_token
      userAuthorizationUri: https://www.facebook.com/dialog/oauth
      tokenName: oauth_token
      authenticationScheme: query
      clientAuthenticationScheme: form
    resource:
      userInfoUri: https://graph.facebook.com/me

SpringBootFacebookLoginExampleApplication.java


@SpringBootApplication
@RestController
@EnableOAuth2Sso
public class SpringBootFacebookLoginExampleApplication {

 @GetMapping("/user")
    public Principal getUser(Principal user) {
     ObjectMapper mapper = new ObjectMapper();
     String user2;
  try {
   user2 = mapper.writeValueAsString(user);
      System.out.println(user2);
  } catch (JsonGenerationException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (JsonMappingException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } catch (IOException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 
        return user;
    }
    public static void main(String[] args) {
        SpringApplication.run(SpringBootFacebookLoginExampleApplication.class, args);
    }

}

OAuth2Configuration.java


@EnableOAuth2Sso
@Configuration
public class OAuth2Configuration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .antMatcher("/**")
                .authorizeRequests()
                .antMatchers("/", "/login**", "/webjars/**", "/error**")
                .permitAll()
                .anyRequest()
                .authenticated();
    }

}

index.html

這邊要用ajex 應該也可
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <title>Demo</title>
    <meta name="description" content=""/>
    <meta name="viewport" content="width=device-width"/>
    <base href="/"/>
    <link rel="stylesheet" type="text/css" href="/webjars/bootstrap/css/bootstrap.min.css"/>
    <script type="text/javascript" src="/webjars/jquery/jquery.min.js"></script>
    <script type="text/javascript" src="/webjars/bootstrap/js/bootstrap.min.js"></script>
</head>
<body>
<h1>Demo</h1>

<div class="container unauthenticated">
    Login With Facebook: <a href="/login">click here</a>
</div>
<div class="container authenticated" style="display:none">
    Logged into Facebook as: <span id="user"></span>
</div>
</div>
<script type="text/javascript">
    $.get("/user", function(data) {
        $("#user").html(data.userAuthentication.details.name);
        $(".unauthenticated").hide()
        $(".authenticated").show()
    });

</script>
</body>
</html>

透過 OAuth2.0 使用 Google登入 範例

OAuth2.0

簡介 OAuth 2.0
在傳統的 Client-Server 架構裡, Client 要拿取受保護的資源 (Protected Resoruce) 的時候,要向 Server 出示使用者 (Resource Owner) 的帳號密碼才行。為了讓第三方應用程式也可以拿到這些 Resources ,則 Resource Owner 要把帳號密碼給這個第三方程式,這樣子就會有以下的問題及限制:
第三方程式必須儲存 Resource Owner 的帳號密碼,通常是明文儲存。
Server 必須支援密碼認證,即使密碼有天生的資訊安全上的弱點。
第三方程式會得到幾乎完整的權限,可以存取 Protected Resources ,而 Resource Owner 沒辦法限制第三方程式可以拿取 Resource 的時效,以及可以存取的範圍 (subset)。
Resource Owner 無法只撤回單一個第三方程式的存取權,而且必須要改密碼才能撤回。
任何第三方程式被破解 (compromized),就會導致使用該密碼的所有資料被破解。
OAuth 解決這些問題的方式,是引入一個認證層 (authorization layer) ,並且把 client 跟 resource owner 的角色分開。在 OAuth 裡面,Client 會先索取存取權,來存取 Resource Owner 擁有的資源,這些資源會放在 Resource Server 上面,並且 Client 會得到一組不同於 Resource Owner 所持有的認證碼 (credentials) 。
Client 會取得一個 Access Token 來存取 Protected Resources ,而非使用 Resource Owner 的帳號密碼。Access Token 是一個字串,記載了特定的存取範圍 (scope) 、時效等等的資訊。Access Token 是從 Authorization Server 拿到的,取得之前會得到 Resource Owner 的許可。Client 用這個 Access Token 來存取 Resource Server 上面的 Protected Resources 。
實際使用的例子:使用者 (Resource Owner) 可以授權印刷服務 (Client) 去相簿網站 (Resource Server) 存取他的私人照片,而不需要把相簿網站的帳號密碼告訴印刷服務。這個使用者會直接授權透過一個相簿網站所信任的伺服器 (Authorization Server) ,核發一個專屬於該印刷服務的認證碼 (Access Token)。
OAuth 是設計來透過 HTTP 使用的。透過 HTTP 以外的通訊協定來使用 OAuth 則是超出 spec 的範圍。
目前我只想大概就是申請帳號這個流程不想再重新再辦一次那麼我有其他社群軟體網站的帳號可以去做登入的動作順便可以取得之前申請帳號所取得申請資料
以下以google login 來作範例

申請 google Oauth憑證


已授權的重新導向 URL
http://localhost:8080/login/oauth2/code/google

pom.xml


 <dependencies>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-oauth2-client</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
   <exclusions>
    <exclusion>
     <groupId>org.junit.vintage</groupId>
     <artifactId>junit-vintage-engine</artifactId>
    </exclusion>
   </exclusions>
  </dependency>
  <dependency>
   <groupId>org.springframework.security</groupId>
   <artifactId>spring-security-test</artifactId>
   <scope>test</scope>
  </dependency>
  <dependency>
      <groupId>org.springframework.security.oauth</groupId>
      <artifactId>spring-security-oauth2</artifactId>
      <version>2.3.3.RELEASE</version>
  </dependency>
  <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.8.5</version>
  </dependency>
 </dependencies>

Controller


import java.io.IOException;
import java.security.Principal;
import java.util.LinkedHashMap;

import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

@RestController
public class Controller {

    @GetMapping("/")
    public String helloWorld() {
        return "you don't need to be logged in";
    }

    @GetMapping("/not-restricted")
    public String notRestricted() {
        return "you don't need to be logged in";
    }

    @GetMapping("/restricted")
    public String restricted() {
        return "if you see this you are logged in";
    }
    
    @RequestMapping(value = "/user")
    public Principal user(Principal principal) throws JsonGenerationException, JsonMappingException, IOException {
     System.out.println(principal.toString());
        ObjectMapper mapper = new ObjectMapper();
         String user = mapper.writeValueAsString(principal);
         System.out.println(user);
       return principal;
    }
    
    @RequestMapping(value = "/username", method = RequestMethod.GET)
    @ResponseBody
    public String currentUserName(Authentication authentication) {
        return authentication.toString();
    }
    
}


OAuth2Demo

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

@SpringBootApplication
public class OAuth2Demo {

 public static void main(String[] args) {
  SpringApplication.run(OAuth2Demo.class, args);
 }

}

SecurityConfig

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {

        http
                .antMatcher("/**").authorizeRequests()
                .antMatchers(new String[]{"/", "/not-restricted"}).permitAll()
                .anyRequest().authenticated()
                .and()
                .oauth2Login();
    }
}

application.yml

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: 601203258465-sv867t=----
            client-secret: y9NP----

demo

http://localhost:8080/#
http://localhost:8080/user


這邊就是google回傳給前端可以直接利用的資料了

Thursday, April 16, 2020

Spring Security 認證中心 重寫為 Redis cache 單台 (一)

上次說要去重寫我們的 認證中心

  • jwt 結合 rsa
  • 改為 redis 集群

驗證流程

也就是說我們的認證中心 以前的話會變成走這樣子的流程
那麼的話就可以發現其實我們的授權中心壓力有點大
網路上找到這幾種流程圖

普通 jwt

1、用戶請求登錄
2、Zuul將請求轉發到授權中心,請求授權
3、授權中心校驗完成,頒發JWT憑證
4、客戶端請求其它功能,攜帶JWT
5、Zuul將JWT交給授權中心校驗,通過後放行
6、用戶請求到達微服務
7、微服務將JWT交給鑑權中心,鑑權同時解析用戶信息
8、鑑權中心返回用戶數據給微服務
9、微服務處理請求,返迴響應

結合 rsa

  1. 我們首先利用RSA生成公鑰和私鑰。私鑰保存在授權中心,公鑰保存在Zuul和各個微服務
  2. 用戶請求登錄
  3. 授權中心校驗,通過後用私鑰對JWT進行簽名加密
  4. 返回JWT給用戶
  5. 用戶攜帶JWT訪問
  6. Zuul直接通過公鑰解密JWT,進行驗證,驗證通過則放行
  7. 請求到達微服務,微服務直接用公鑰解析JWT,獲取用戶信息,無需訪問授權中心
可能只會有共同一組密鑰問題,不過這樣就不能進行分開鑑權了。

那我們只能這樣動刀了
直接把 ehcache 換成 redis
那麼授權中心就負責頒發 jwt ,在zuul網關訪問服務的時候我們可以把緩存在 redis 的 password 和 username 拿來查詢
假設查詢到代表在頒發jwt的時候 將會進行加密,用自己的密碼去加密,解密的時候將會解碼我們的jwt token 去解碼中間payloadJson 取得 我們的 username 再來就是拿我們的 username 再去查詢我們的 redis 看有沒有緩存,有的話代表已經登入過 那麼我們就可以直接去用我們的 username
if (! JwtUtil.verify(token, username, userDetails.getPassword()))
拿我們傳過來的 token 配合 剛剛查詢的 username 在去從我們緩存在 userDetails 裡面的 password 再去重簽一次

也就是說你偽造jwt也沒用,假設你沒登入過,你就不會緩存在 redis,你的token 又是透過你的 password 去簽的
所以 偽造jwt 要先猜對你的 password 然後又要在你猜對帳號然後又已經登入帳號的時候 也就是 redis seession時間存活時,這樣才能剛好進去。

SecurityConfiguration 新增 bean

  • 新增 RedisTemplate
  • 重寫我們的 SpringCacheBasedUserCache 為 springzzz
  • 重寫 UserDetails 為 CustomUserDetails
  • 傳入 RedisTemplate 去調用我們的 redis

// 开启 Security
@EnableWebSecurity
// 开启注解配置支持
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsServiceImpl;

    // Spring Boot 的 CacheManager,这里我们使用 JCache
    @Autowired
    private CacheManager cacheManager;
    @Bean
    //指定我們的 redistemplate key 為 string value 為 CustomUserDetails
    public RedisTemplate<String, CustomUserDetails> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, CustomUserDetails> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        // 使用Jackson2JsonRedisSerialize 替换默认序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
 //       objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        ///
        //
        //
        //
        //
        //
        
        objectMapper.registerModule(new SimpleModule().addDeserializer(
                SimpleGrantedAuthority.class, new SimpleGrantedAuthorityDeserializer()));
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        // 设置value的序列化规则和 key的序列化规则
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
    @Autowired
    private    RedisTemplate<String, CustomUserDetails> redisTemplate;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 开启跨域
        http.cors()
                .and()
                // security 默认 csrf 是开启的,我们使用了 token ,这个也没有什么必要了
                .csrf().disable()
                .authorizeRequests()
                // 默认所有请求通过,但是我们要在需要权限的方法加上安全注解,这样比写死配置灵活很多
                .anyRequest().permitAll()
                .and()
                // 添加自己编写的两个过滤器
                .addFilter(new JwtAuthenticationFilter(authenticationManager()))
                .addFilter(new JwtAuthorizationFilter(authenticationManager(), cachingUserDetailsService(userDetailsServiceImpl)))
                // 前后端分离是 STATELESS,故 session 使用该策略
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
   
    }

    // 此处配置 AuthenticationManager,并且实现缓存
    //在緩存 usercache 讀入我們的
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 对自己编写的 UserDetailsServiceImpl 进一步包装,实现缓存
        CachingUserDetailsService cachingUserDetailsService = cachingUserDetailsService(userDetailsServiceImpl);
        // jwt-cache 我们在 ehcache.xml 配置文件中有声明
        UserCache userCache = new springzz (cacheManager.getCache("jwt-cache"),  redisTemplate);
        
        cachingUserDetailsService.setUserCache(userCache);
        System.out.println("test");
        /*
        security 默认鉴权完成后会把密码抹除,但是这里我们使用用户的密码来作为 JWT 的生成密钥,
        如果被抹除了,在对 JWT 进行签名的时候就拿不到用户密码了,故此处关闭了自动抹除密码。
         */
        auth.eraseCredentials(false);
        auth.userDetailsService(cachingUserDetailsService);
    }

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

    /*
    此处我们实现缓存的时候,我们使用了官方现成的 CachingUserDetailsService ,但是这个类的构造方法不是 public 的,
    我们不能够正常实例化,所以在这里进行曲线救国。
     */
    private CachingUserDetailsService cachingUserDetailsService(UserDetailsServiceImpl delegate) {
        
        Constructor<CachingUserDetailsService> ctor = null;
        try {
            ctor = CachingUserDetailsService.class.getDeclaredConstructor(UserDetailsService.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        Assert.notNull(ctor, "CachingUserDetailsService constructor is null");
        ctor.setAccessible(true);
  
        return BeanUtils.instantiateClass(ctor, delegate);
    }
}

SimpleGrantedAuthorityDeserializer

重寫 反向序列SimpleGrantedAuthorityDeserializer

class SimpleGrantedAuthorityDeserializer extends StdDeserializer<SimpleGrantedAuthority> {
    public SimpleGrantedAuthorityDeserializer() {
        super(SimpleGrantedAuthority.class);
    }
    @Override
    public SimpleGrantedAuthority deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        JsonNode tree = p.getCodec().readTree(p);
        return new SimpleGrantedAuthority(tree.get("authority").textValue());
    }
}

springzzzz

使用構造好的 redistemplate
這邊主要是控制 cache
redisTemplate.opsForValue().set(customUserDetails.getUsername(),customUserDetails);
/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.inlighting.security.security;


/**
 * Caches {@link UserDetails} instances in a Spring defined {@link Cache}.
 *
 * @author Marten Deinum
 * @since 3.2
 */
public class springzz implements UserCache {

 // ~ Static fields/initializers
 // =====================================================================================

 public static final Log logger = LogFactory.getLog(springzz.class);

 // ~ Instance fields
 // ================================================================================================
   
//  @Autowired
//     RedisTemplate<Object, Object> template ;
//  
//  @Bean
//     JedisConnectionFactory jedisConnectionFactory() {
//         return new JedisConnectionFactory();
//     }

  
//     @Bean
//     RedisTemplate< String, Object > redisTemplate() {
//         final RedisTemplate< String, Object > template =  new RedisTemplate< String, Object >();
//         template.setConnectionFactory( jedisConnectionFactory() );
//         template.setKeySerializer( new StringRedisSerializer() );
//      //   template.setHashValueSerializer( new GenericToStringSerializer< UserDetails >( UserDetails.class ) );
//    //     template.setValueSerializer( new GenericToStringSerializer< UserDetails >( UserDetails.class ) );
//
//
//         template.setValueSerializer(new springSessionDefaultRedisSerializer());
//         //template.setValueSerializer(new JsonRedisSerializer());
//
//         return template;
//     }
//  
//   @Bean
//      public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//          RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
//          redisTemplate.setConnectionFactory(redisConnectionFactory);
//
//          // 使用Jackson2JsonRedisSerialize 替换默认序列化
//          Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//
//          ObjectMapper objectMapper = new ObjectMapper();
//   //       objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//          objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
//
//          jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//
//          // 设置value的序列化规则和 key的序列化规则
//          redisTemplate.setKeySerializer(new StringRedisSerializer());
//          redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
//          redisTemplate.afterPropertiesSet();
//          return redisTemplate;
//      }
  
 public final Cache cache;
    public final RedisTemplate<String, CustomUserDetails> redisTemplate;
 // ~ Constructors
 // ===================================================================================================

 public springzz(Cache cache, RedisTemplate<String, CustomUserDetails> redisTemplate) throws Exception {
  Assert.notNull(cache, "cache mandatory");
  Assert.notNull(redisTemplate, "redisTemplate mandatory");
  this.redisTemplate = redisTemplate;
  this.cache = cache;
 }

 // ~ Methods
 // ========================================================================================================

 public UserDetails getUserFromCache(String username) {
  Cache.ValueWrapper element = username != null ? cache.get(username) : null;

  if (logger.isDebugEnabled()) {
   logger.debug("Cache hit: " + (element != null) + "; username: " + username);
   
  }
       

  //System.out.println("im here"+((UserDetails)cache.get(username)).getPassword().toString() );
  if (element == null) {
   System.out.println("no here"+username);
   return null;
  }
  else {
  System.out.println("im here");
     
  CustomUserDetails result = (CustomUserDetails) redisTemplate.opsForValue().get(username);
  System.out.println(result.getUsername());
   return (UserDetails) result;
//  return result;
  }
 }


 public void putUserInCache(UserDetails user) {
  if (logger.isDebugEnabled()) {
   logger.debug("Cache put: " + user.getUsername());
 
  }
//  System.out.println("Cache put:"+ user.getUsername());
//  System.out.println("Cache put:"+ user.getPassword());
//  System.out.println("Cache put:"+ user.getAuthorities());
  
  //UserDetails user2 = user;
  //重寫userdetails
  CustomUserDetails customUserDetails = new CustomUserDetails (user.getUsername(),user.getPassword(),user.getAuthorities());
 // Admin tmp = new CustomUserDetails(customUserDetails);
      System.out.println(customUserDetails.getUsername());
      System.out.println(customUserDetails.getAuthorities());
      
      redisTemplate.opsForValue().set(customUserDetails.getUsername(),customUserDetails);
         //原本opsForValue()是只能操作字符串的.现在就可以操作对象了
//         customUserDetails result = (customUserDetails) template.opsForValue().get(customUserDetails.getUsername()+"");
//         System.out.println(result.toString());
 // cache.put(user.getUsername(), user);
 }

 public void removeUserFromCache(UserDetails user) {
  if (logger.isDebugEnabled()) {
   logger.debug("Cache remove: " + user.getUsername());
  }

  this.removeUserFromCache(user.getUsername());
 }

 public void removeUserFromCache(String username) {
  
  cache.evict(username);
 }
}

重寫後預設UserDetails

CustomUserDetails

@JsonSerialize
@JsonIgnoreProperties(ignoreUnknown = true)
public class CustomUserDetails extends Admin implements UserDetails {

    public CustomUserDetails() {
       super();
    }
//
//
    public CustomUserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities) {
  // TODO Auto-generated constructor stub
     super( username, password, authorities);
 }
//
//    @Override
// public void setAuthorityList(Collection<? extends GrantedAuthority> authorityList) {
//        List<SimpleGrantedAuthority> listGrantedAuth = new ArrayList<>();
//        authorityList.forEach(auth -> {
//            listGrantedAuth.add(new SimpleGrantedAuthority(auth.toString()));
//        });
//        super.setAuthorityList(listGrantedAuth);
// }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> listGrantedAuth = new ArrayList<>();
        super.getAuthorities().forEach(auth -> {
            listGrantedAuth.add(new SimpleGrantedAuthority(auth.toString()));
        });
        return listGrantedAuth;
    }
 

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }



 @Override
 public String getPassword() {
  // TODO Auto-generated method stub
     return super.getPassword();
 }



 @Override
 public String getUsername() {
  // TODO Auto-generated method stub
  return super.getUsername();
 }
}

admin

public class Admin implements Serializable {

    private String username;
    private String password;
    private Collection<? extends GrantedAuthority> authorities;
    

 public Admin() {
  super();
  // TODO Auto-generated constructor stub
 }

 public Admin(String username, String password, Collection<? extends GrantedAuthority> authorities) {
  super();
  this.username = username;
  this.password = password;
  this.authorities = authorities;
 }

 public String getUsername() {
  return username;
 }
 public void setUsername(String username) {
  this.username = username;
 }
 public String getPassword() {
  return password;
 }
 public void setPassword(String password) {
  this.password = password;
 }

 public Collection<? extends GrantedAuthority> getAuthorities() {
  return authorities;
 }


    // default constructor

}

分析redis 分別存入重寫 CustomUserDetails 原本與預設UserDetails

原本與預設

UserDetails

[“org.springframework.security.core.userdetails.User”,{“password”:"$2a10AQol1A.LkxoJ5dEzS5o5E.QG9jD.hncoeCGdVaMQZaiYZ98V/JyRq",“username”:“jack”,“authorities”:[“java.util.Collections$UnmodifiableSet”,[[“org.springframework.security.core.authority.SimpleGrantedAuthority”,{“authority”:“ROLE_USER”}]]],“accountNonExpired”:true,“accountNonLocked”:true,“credentialsNonExpired”:true,“enabled”:true}]

CustomUserDetails

[“org.inlighting.security.security.CustomUserDetails”,{“username”:“jack”,“password”:"$2a10AQol1A.LkxoJ5dEzS5o5E.QG9jD.hncoeCGdVaMQZaiYZ98V/JyRq",“authorities”:[“java.util.ArrayList”,[[“org.springframework.security.core.authority.SimpleGrantedAuthority”,{“authority”:“ROLE_USER”}]]],“enabled”:true,“accountNonLocked”:true,“accountNonExpired”:true,“credentialsNonExpired”:true}]
預設 UserDetails
Could not read JSON: Cannot construct instance of org.springframework.security.core.authority.SimpleGrantedAuthority (although at least one Creator exists):
No Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator
報錯

分別原因

  1. construct 忘記加 建構元
  2. SimpleGrantedAuthority  不能進行反向序列org.springframework.core.codec.DecodingException: JSON decoding error: Cannot construct instance of org.springframework.security.core.authority.SimpleGrantedAuthority (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator);
    我們的org.springframework.security.core.authority.SimpleGrantedAuthority 不能透過 json
    https://blog.csdn.net/m0_37893932/article/details/78259288
    進行反向序列。
    https://blog.csdn.net/weixin_34402408/article/details/92134715



到此我們的 改動 ehcache 就改為 redis 接下來就是看 RedisTemplate 如何去讀 redis集群 (應該蠻簡單?
也就是說我們把 db 的 二級緩存 ehcache 換成我們的 redis
新的問題來,我們的認證中心,是否可以換成集群 ,只要確保我們的 redis session 共享就可以了吧! 過幾天再來重構成 集群版。