Spring-Security+OAuth2+redis implementiert die Passwortanmeldung

1. OAuth2-Authentifizierungsmodus

        Insgesamt gibt es vier Authentifizierungsmethoden: Autorisierungscodemodus, Passwortmodus, vereinfachter Modus und Clientmodus. Um eine einmalige Anmeldung zu erreichen, ist die beliebtere Methode die Verwendung der JWT-Methode. JWT ist zustandslos, QIT selbst kann Informationen übertragen, sodass der Server seine Informationen nicht speichern muss Der Benutzer kann immer darauf zugreifen, daher kann die Exit-Funktion nicht implementiert werden. Wenn Sie die Abmeldefunktion implementieren möchten, müssen Sie Token-Informationen auf der Serverseite speichern, was gegen die ursprüngliche Absicht der Verwendung der JWT-Authentifizierung verstößt.

2. Erstellen Sie eine Sicherheitskonfigurationsklasse

        Fügen Sie die erforderlichen Toolklassen ein und konfigurieren Sie Freigabe- und Authentifizierungsregeln in dieser Klasse

/**
 * SecurityConfiguration 配置类
 */
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
//    注入Redis连接工厂
    @Resource
    private RedisConnectionFactory redisConnectionFactory;

//    Redis仓库类,初始化RedisTokenStore用于将Token存储到Redis
    @Bean
    public RedisTokenStore redisTokenStore(){
        RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
        redisTokenStore.setPrefix("TOKEN:"); //设置KEY的层级前缀,方便查询
        return redisTokenStore;
    }

//    初始化密码编辑器,用MD5加密密码
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new PasswordEncoder() {
            /**
             * 加密
             * @param rawPassword 原始密码
             * @return
             */
            @Override
            public String encode(CharSequence rawPassword) {
                return DigestUtil.md5Hex(rawPassword.toString());
            }

            /**
             * 校验密码
             * @param rawPassword 原始密码
             * @param encodedPassword 加密密码
             * @return
             */
            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                return DigestUtil.md5Hex(rawPassword.toString()).equals(encodedPassword);
            }
        };
    }

//    初始化认证管理对象,密码登录方式需要用到
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

//    放行和认证规则
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // csrf认为除了get以外的请求都是不安全的,禁用
        http.csrf().disable()
                .authorizeRequests()
//                放行的请求
                .antMatchers("/oauth/**", "/actuator/**").permitAll()
                .and()
                .authorizeRequests()
//                其他请求必须认证才能访问
                .anyRequest().authenticated();
    }
}

3. Implementieren Sie Ihre eigene UserDetails-Schnittstelle.

/**
 * 登录认证对象
 */
@Data
public class SignInIdentity implements UserDetails {
    private Integer id;
    private String username;
    private String password;
    private String roles;
    private Boolean isValid;
    // 角色集合
    private List<GrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if(StrUtil.isNotBlank(this.roles)) {
            // 获取数据库中的角色信息
            this.authorities = Stream.of(this.roles.split(",")).map(role -> new SimpleGrantedAuthority(role))
                    .collect(Collectors.toList());
        }else{
//            如果角色为空则设置为ROLE_USER
            this.authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");
        }

        return this.authorities;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.username;
    }

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

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

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

    @Override
    public boolean isEnabled() {
        return this.isValid;
    }
}

4. Implementieren Sie Ihre eigene UserDetailsService-Schnittstelle

        Diese Schnittstelle stellt eine Methode „loadUserByUsername“ bereit. Wir erhalten unsere Benutzerinformationen im Allgemeinen durch Erweitern dieser Schnittstelle, und der Rückgabewert sind die im vorherigen Schritt definierten UserDetails.

@Service
public class UserService implements UserDetailsService {
    @Resource
    private DinersMapper dinersMapper;
    @Resource
    private PasswordEncoder passwordEncoder;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        AssertUtil.isNotEmpty(username,"请输入用户名");
        Diners user = dinersMapper.selectByAccountInfo(username);
        if(user == null){
            throw new UsernameNotFoundException("用户名或密码错误,请重新输入");
        }
        // 初始化认证登录对象
        SignInIdentity signInIdentity = new SignInIdentity();
        BeanUtils.copyProperties(user, signInIdentity);
        signInIdentity.setPassword(passwordEncoder.encode(user.getPassword()));
        return signInIdentity;
    }
}

5. Erstellen Sie eine Authentifizierungsserver-Konfigurationsklasse

# Oauth2
client:
  oauth2:
    client-id: appId #客户端标识ID
    secret: 123456 #客户端安全码
#    授权类型
    grant-types:
      - password
      - refresh_token
    refresh-token-validity-time: 2000
#    token有效时间
    token-validity-time: 3600
#    客户端访问范围
    scopes:
      - api
      - all
/**
 * 授权服务
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    @Resource
    private ClientOauth2DataConfiguration clientOauth2DataConfiguration;
    @Resource
    private PasswordEncoder passwordEncoder;
//    认证管理对象
    @Resource
    private AuthenticationManager authenticationManager;
    @Resource
    private RedisTokenStore redisTokenStore;
    // 登录校验
    @Resource
    private UserService userService;
    /**
     * 配置令牌端点安全约束
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
//        允许访问token的公钥,默认/oauth/token_key 是受保护的
        security.tokenKeyAccess("permitAll()")
//                允许检查token状态,默认/oauth/check_token 是受保护的
                .checkTokenAccess("permitAll()");
    }

    /**
     * 客户端配置 - 授权模型
     * 配置被允许访问这个认证服务器的客户端信息
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient(clientOauth2DataConfiguration.getClientId()) // 客户端标识ID
                .secret(passwordEncoder.encode(clientOauth2DataConfiguration.getSecret())) // 客户端安全码
                .authorizedGrantTypes(clientOauth2DataConfiguration.getGrantTypes()) // 授权类型
                .accessTokenValiditySeconds(clientOauth2DataConfiguration.getTokenValidityTime()) // token有效期
                .refreshTokenValiditySeconds(clientOauth2DataConfiguration.getRefreshTokenValidityTime()) //刷新token的有效期
                .scopes(clientOauth2DataConfiguration.getScopes()); // 客户端访问范围
    }

    /**
     * 配置授权以及令牌的访问端点和令牌服务
     * 授权服务器端点配置器
     * @param endpoints
     * @throws Exception
     */

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 认证器
        // password需要配置authenticationManager
        endpoints.authenticationManager(authenticationManager)
                // 具体的登录方法
                .userDetailsService(userService)
                // token存储方式
                .tokenStore(redisTokenStore)
                // 令牌增强对象,增强返回的结果
                .tokenEnhancer((accessToken, authentication) -> {
                    SignInIdentity signInIdentity = (SignInIdentity) authentication.getPrincipal();
                    LinkedHashMap<String, Object> map = new LinkedHashMap<>();
//                    追加额外信息
                    map.put("username",signInIdentity.getUsername());
                    map.put("authorities", signInIdentity.getAuthorities());
                    DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
                    token.setAdditionalInformation(map);
                    return token;
                 });
    }
}

        Unter diesen zeigt die Authentifizierung die aktuelle Authentifizierungssituation an und es können Benutzerdetails (Benutzerinformationen), Anmeldeinformationen (Passwort), isAuthenticated (ob authentifiziert wurde) und Principal (Benutzer) abgerufen werden. Wenn der Principal authentifiziert ist, werden UserDetails zurückgegeben. Wenn er nicht authentifiziert ist, ist es der Benutzername.

6. Schreiben Sie die Anmeldemethode neu

/**
 * Oauth2控制器
 */
@RestController
@RequestMapping("/oauth")
public class OAuthController {
    @Resource
    private TokenEndpoint tokenEndpoint;
    @Resource
    private HttpServletRequest request;
    @PostMapping("/token")
    public ResultInfo postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
        return custom(tokenEndpoint.postAccessToken(principal, parameters).getBody());
    }
    private ResultInfo custom(OAuth2AccessToken accessToken){
        DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
        Map<String, Object> data = new LinkedHashMap(token.getAdditionalInformation());
        data.put("accessToken",token.getValue());
        data.put("expireIn", token.getExpiresIn());
        data.put("scopes", token.getScope());
        if(token.getRefreshToken() != null) {
            data.put("refreshToken", token.getRefreshToken().getValue());
        }
        return ResultInfoUtil.buildSuccess(request.getServletPath(), data);
    }
}

Supongo que te gusta

Origin blog.csdn.net/qq_33235279/article/details/131724104
Recomendado
Clasificación