最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring Boot 2.0 整合 Spring Security OAuth2的完整實現(xiàn)方案

 更新時間:2026年03月20日 10:04:07   作者:油墨香^_^  
OAuth 2.0是一個行業(yè)標準的授權協(xié)議,它允許用戶在不將用戶名和密碼提供給第三方應用的情況下,授權第三方應用訪問用戶存儲在服務提供方的資源,本文給大家介紹Spring Boot 2.0 整合 Spring Security OAuth2的完整實現(xiàn)方案,感興趣的朋友一起看看吧

1. OAuth2 基礎概念與原理

1.1 OAuth2 是什么

OAuth 2.0(開放授權 2.0)是一個行業(yè)標準的授權協(xié)議,它允許用戶在不將用戶名和密碼提供給第三方應用的情況下,授權第三方應用訪問用戶存儲在服務提供方的資源。

1.2 OAuth2 核心角色

  • 資源所有者 (Resource Owner):能夠授權訪問受保護資源的實體,通常是最終用戶
  • 客戶端 (Client):請求訪問受保護資源的應用程序
  • 授權服務器 (Authorization Server):在認證資源所有者并獲取授權后,向客戶端頒發(fā)訪問令牌的服務器
  • 資源服務器 (Resource Server):托管受保護資源的服務器,能夠接受和響應使用訪問令牌的受保護資源請求

1.3 OAuth2 授權模式

  • 授權碼模式 (Authorization Code):最安全、最常用的模式,適用于有后端的 Web 應用
  • 簡化模式 (Implicit):適用于純前端 SPA 應用
  • 密碼模式 (Resource Owner Password Credentials):適用于信任的客戶端,如官方應用
  • 客戶端模式 (Client Credentials):適用于客戶端訪問自己的資源

2. 環(huán)境準備與項目搭建

2.1 創(chuàng)建 Spring Boot 項目

使用 Spring Initializr 創(chuàng)建項目,選擇以下依賴:

  • Spring Web
  • Spring Security
  • Spring Security OAuth2
  • Spring Data JPA
  • MySQL Driver
  • Lombok

2.2 Maven 依賴配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
        <relativePath/>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>spring-security-oauth2-demo</artifactId>
    <version>1.0.0</version>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!-- Spring Boot Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Spring Security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- OAuth2 自動配置 -->
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
            <version>2.1.0.RELEASE</version>
        </dependency>
        <!-- Spring Data JPA -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- MySQL 驅動 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- JJWT for JWT tokens -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3. 數(shù)據(jù)庫設計

3.1 用戶表結構

CREATE TABLE `sys_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL COMMENT '用戶名',
  `password` varchar(100) NOT NULL COMMENT '密碼',
  `email` varchar(100) DEFAULT NULL COMMENT '郵箱',
  `phone` varchar(20) DEFAULT NULL COMMENT '手機號',
  `enabled` tinyint(1) DEFAULT '1' COMMENT '狀態(tài):1-有效,0-禁用',
  `account_non_expired` tinyint(1) DEFAULT '1' COMMENT '賬戶是否過期',
  `credentials_non_expired` tinyint(1) DEFAULT '1' COMMENT '密碼是否過期',
  `account_non_locked` tinyint(1) DEFAULT '1' COMMENT '賬戶是否鎖定',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改時間',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用戶表';

3.2 角色表結構

CREATE TABLE `sys_role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(50) NOT NULL COMMENT '角色名稱',
  `role_code` varchar(50) NOT NULL COMMENT '角色編碼',
  `description` varchar(100) DEFAULT NULL COMMENT '描述',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_role_code` (`role_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';

3.3 用戶角色關聯(lián)表

CREATE TABLE `sys_user_role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NOT NULL COMMENT '用戶ID',
  `role_id` bigint(20) NOT NULL COMMENT '角色ID',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_role_id` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用戶角色關聯(lián)表';

3.4 OAuth2 客戶端表

CREATE TABLE `oauth_client_details` (
  `client_id` varchar(256) NOT NULL COMMENT '客戶端ID',
  `resource_ids` varchar(256) DEFAULT NULL COMMENT '資源ID',
  `client_secret` varchar(256) DEFAULT NULL COMMENT '客戶端密鑰',
  `scope` varchar(256) DEFAULT NULL COMMENT '權限范圍',
  `authorized_grant_types` varchar(256) DEFAULT NULL COMMENT '授權類型',
  `web_server_redirect_uri` varchar(256) DEFAULT NULL COMMENT '重定向URI',
  `authorities` varchar(256) DEFAULT NULL COMMENT '權限',
  `access_token_validity` int(11) DEFAULT NULL COMMENT '訪問令牌有效期',
  `refresh_token_validity` int(11) DEFAULT NULL COMMENT '刷新令牌有效期',
  `additional_information` varchar(4096) DEFAULT NULL COMMENT '附加信息',
  `autoapprove` varchar(256) DEFAULT NULL COMMENT '自動批準',
  PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='OAuth2客戶端表';

4. 實體類設計

4.1 用戶實體

package com.example.oauth2.entity;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@Data
@Entity
@Table(name = "sys_user")
public class SysUser implements UserDetails {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "username", unique = true, nullable = false, length = 50)
    private String username;
    @Column(name = "password", nullable = false, length = 100)
    private String password;
    @Column(name = "email", length = 100)
    private String email;
    @Column(name = "phone", length = 20)
    private String phone;
    @Column(name = "enabled")
    private Boolean enabled = true;
    @Column(name = "account_non_expired")
    private Boolean accountNonExpired = true;
    @Column(name = "credentials_non_expired")
    private Boolean credentialsNonExpired = true;
    @Column(name = "account_non_locked")
    private Boolean accountNonLocked = true;
    @Column(name = "create_time")
    private Date createTime;
    @Column(name = "update_time")
    private Date updateTime;
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
        name = "sys_user_role",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private List<SysRole> roles;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roles.stream()
                .map(role -> new SimpleGrantedAuthority(role.getRoleCode()))
                .collect(Collectors.toList());
    }
    @Override
    public boolean isAccountNonExpired() {
        return accountNonExpired;
    }
    @Override
    public boolean isAccountNonLocked() {
        return accountNonLocked;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        return credentialsNonExpired;
    }
    @Override
    public boolean isEnabled() {
        return enabled;
    }
}

4.2 角色實體

package com.example.oauth2.entity;
import lombok.Data;
import javax.persistence.*;
import java.util.Date;
@Data
@Entity
@Table(name = "sys_role")
public class SysRole {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "role_name", nullable = false, length = 50)
    private String roleName;
    @Column(name = "role_code", nullable = false, length = 50)
    private String roleCode;
    @Column(name = "description", length = 100)
    private String description;
    @Column(name = "create_time")
    private Date createTime;
}

5. 數(shù)據(jù)訪問層

5.1 用戶 Repository

package com.example.oauth2.repository;
import com.example.oauth2.entity.SysUser;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<SysUser, Long> {
    Optional<SysUser> findByUsername(String username);
    Boolean existsByUsername(String username);
    Boolean existsByEmail(String email);
    @Query("SELECT u FROM SysUser u LEFT JOIN FETCH u.roles WHERE u.username = :username")
    Optional<SysUser> findByUsernameWithRoles(@Param("username") String username);
}

5.2 角色 Repository

package com.example.oauth2.repository;
import com.example.oauth2.entity.SysRole;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface RoleRepository extends JpaRepository<SysRole, Long> {
    Optional<SysRole> findByRoleCode(String roleCode);
}

6. 安全配置

6.1 Spring Security 配置

package com.example.oauth2.config;
import com.example.oauth2.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    /**
     * 密碼編碼器
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    /**
     * 認證管理器
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    /**
     * 配置用戶認證
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());
    }
    /**
     * HTTP安全配置
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/oauth/**", "/login/**", "/logout/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin().permitAll()
            .and()
            .csrf().disable();
    }
}

6.2 用戶詳情服務

package com.example.oauth2.service;
import com.example.oauth2.entity.SysUser;
import com.example.oauth2.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = userRepository.findByUsernameWithRoles(username)
                .orElseThrow(() -> new UsernameNotFoundException("用戶不存在: " + username));
        log.info("用戶 {} 登錄成功,角色: {}", username, 
                user.getRoles().stream().map(role -> role.getRoleCode()).toArray());
        return user;
    }
}

7. OAuth2 授權服務器配置

7.1 授權服務器配置

package com.example.oauth2.config;
import com.example.oauth2.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import javax.sql.DataSource;
import java.util.Arrays;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;
    /**
     * 配置客戶端詳情服務
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 使用JDBC存儲客戶端信息
        clients.withClientDetails(jdbcClientDetailsService());
        // 內(nèi)存方式配置客戶端(測試用)
        // clients.inMemory()
        //         .withClient("client")
        //         .secret(passwordEncoder.encode("secret"))
        //         .authorizedGrantTypes("password", "refresh_token")
        //         .scopes("all")
        //         .accessTokenValiditySeconds(3600)
        //         .refreshTokenValiditySeconds(86400);
    }
    /**
     * 配置令牌端點安全約束
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
            .tokenKeyAccess("permitAll()")                    // 公開/oauth/token_key接口
            .checkTokenAccess("isAuthenticated()")           // 認證后可訪問/oauth/check_token
            .allowFormAuthenticationForClients();            // 允許表單認證
    }
    /**
     * 配置授權端點
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 令牌增強鏈
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(
                Arrays.asList(tokenEnhancer(), jwtAccessTokenConverter()));
        endpoints
            .authenticationManager(authenticationManager)    // 認證管理器
            .userDetailsService(userDetailsService)          // 用戶詳情服務
            .tokenStore(tokenStore())                        // 令牌存儲方式
            .tokenEnhancer(tokenEnhancerChain)               // 令牌增強
            .accessTokenConverter(jwtAccessTokenConverter()) // JWT轉換器
            .reuseRefreshTokens(false);                      // 是否重用刷新令牌
    }
    /**
     * 令牌存儲 - 使用Redis
     */
    @Bean
    public TokenStore tokenStore() {
        // 使用Redis存儲令牌
        return new RedisTokenStore(redisConnectionFactory);
        // 使用JWT存儲令牌
        // return new JwtTokenStore(jwtAccessTokenConverter());
    }
    /**
     * 客戶端詳情服務 - 使用JDBC
     */
    @Bean
    public ClientDetailsService jdbcClientDetailsService() {
        JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        clientDetailsService.setPasswordEncoder(passwordEncoder);
        return clientDetailsService;
    }
    /**
     * JWT令牌轉換器
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        // 設置JWT簽名密鑰
        converter.setSigningKey("my-signing-key");
        return converter;
    }
    /**
     * JWT令牌增強器
     */
    @Bean
    public TokenEnhancer tokenEnhancer() {
        return new CustomTokenEnhancer();
    }
}

7.2 自定義令牌增強器

package com.example.oauth2.config;
import com.example.oauth2.entity.SysUser;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import java.util.HashMap;
import java.util.Map;
public class CustomTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        Map<String, Object> additionalInfo = new HashMap<>();
        // 添加自定義信息到令牌中
        Object principal = authentication.getUserAuthentication().getPrincipal();
        if (principal instanceof SysUser) {
            SysUser user = (SysUser) principal;
            additionalInfo.put("user_id", user.getId());
            additionalInfo.put("username", user.getUsername());
            additionalInfo.put("email", user.getEmail());
        } else if (principal instanceof User) {
            User user = (User) principal;
            additionalInfo.put("username", user.getUsername());
        }
        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
        return accessToken;
    }
}

8. OAuth2 資源服務器配置

8.1 資源服務器配置

package com.example.oauth2.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import javax.annotation.Resource;
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    private static final String RESOURCE_ID = "resource-server";
    @Resource
    private TokenStore tokenStore;
    /**
     * 配置資源ID和令牌服務
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources
            .resourceId(RESOURCE_ID)
            .tokenStore(tokenStore)
            .stateless(true); // 無狀態(tài)模式
    }
    /**
     * 配置資源權限規(guī)則
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/api/public/**").permitAll()           // 公開接口
            .antMatchers("/api/admin/**").hasRole("ADMIN")       // 需要ADMIN角色
            .antMatchers("/api/user/**").hasRole("USER")         // 需要USER角色
            .anyRequest().authenticated()                        // 其他接口需要認證
            .and()
            .csrf().disable();
    }
}

9. JWT 令牌配置

9.1 JWT 工具類

package com.example.oauth2.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Slf4j
@Component
public class JwtTokenUtil {
    @Value("${jwt.secret:mySecretKey}")
    private String secret;
    @Value("${jwt.expiration:86400}")
    private Long expiration;
    /**
     * 從令牌中獲取用戶名
     */
    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }
    /**
     * 從令牌中獲取過期時間
     */
    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }
    /**
     * 從令牌中獲取聲明
     */
    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }
    /**
     * 從令牌中獲取所有聲明
     */
    private Claims getAllClaimsFromToken(String token) {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }
    /**
     * 檢查令牌是否過期
     */
    private Boolean isTokenExpired(String token) {
        Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }
    /**
     * 生成令牌
     */
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return doGenerateToken(claims, userDetails.getUsername());
    }
    /**
     * 生成令牌
     */
    private String doGenerateToken(Map<String, Object> claims, String subject) {
        Date createdDate = new Date();
        Date expirationDate = new Date(createdDate.getTime() + expiration * 1000);
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(createdDate)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }
    /**
     * 驗證令牌
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        String username = getUsernameFromToken(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }
    /**
     * 刷新令牌
     */
    public String refreshToken(String token) {
        String username = getUsernameFromToken(token);
        return generateToken(new org.springframework.security.core.userdetails.User(
                username, "", java.util.Collections.emptyList()));
    }
}

10. 控制器開發(fā)

10.1 用戶控制器

package com.example.oauth2.controller;
import com.example.oauth2.entity.SysUser;
import com.example.oauth2.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/api/user")
public class UserController {
    @Autowired
    private UserService userService;
    /**
     * 獲取當前用戶信息
     */
    @GetMapping("/me")
    public ResponseEntity<SysUser> getCurrentUser() {
        String username = SecurityContextHolder.getContext().getAuthentication().getName();
        SysUser user = userService.findByUsername(username);
        return ResponseEntity.ok(user);
    }
    /**
     * 獲取所有用戶(需要ADMIN權限)
     */
    @PreAuthorize("hasRole('ADMIN')")
    @GetMapping("/list")
    public ResponseEntity<List<SysUser>> getAllUsers() {
        List<SysUser> users = userService.findAll();
        return ResponseEntity.ok(users);
    }
    /**
     * 根據(jù)ID獲取用戶
     */
    @PreAuthorize("hasRole('ADMIN')")
    @GetMapping("/{id}")
    public ResponseEntity<SysUser> getUserById(@PathVariable Long id) {
        SysUser user = userService.findById(id);
        return ResponseEntity.ok(user);
    }
    /**
     * 創(chuàng)建用戶
     */
    @PreAuthorize("hasRole('ADMIN')")
    @PostMapping
    public ResponseEntity<SysUser> createUser(@RequestBody SysUser user) {
        SysUser createdUser = userService.createUser(user);
        return ResponseEntity.ok(createdUser);
    }
    /**
     * 更新用戶
     */
    @PreAuthorize("hasRole('ADMIN')")
    @PutMapping("/{id}")
    public ResponseEntity<SysUser> updateUser(@PathVariable Long id, @RequestBody SysUser user) {
        user.setId(id);
        SysUser updatedUser = userService.updateUser(user);
        return ResponseEntity.ok(updatedUser);
    }
    /**
     * 刪除用戶
     */
    @PreAuthorize("hasRole('ADMIN')")
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.ok().build();
    }
}

10.2 公開接口控制器

package com.example.oauth2.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/public")
public class PublicController {
    @GetMapping("/info")
    public Map<String, String> getPublicInfo() {
        Map<String, String> result = new HashMap<>();
        result.put("message", "這是一個公開接口,無需認證即可訪問");
        result.put("timestamp", String.valueOf(System.currentTimeMillis()));
        return result;
    }
    @GetMapping("/health")
    public Map<String, String> healthCheck() {
        Map<String, String> result = new HashMap<>();
        result.put("status", "UP");
        result.put("service", "oauth2-service");
        return result;
    }
}

11. 服務層實現(xiàn)

11.1 用戶服務

package com.example.oauth2.service;
import com.example.oauth2.entity.SysUser;
import com.example.oauth2.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.List;
import java.util.Optional;
@Slf4j
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private PasswordEncoder passwordEncoder;
    /**
     * 根據(jù)用戶名查找用戶
     */
    public SysUser findByUsername(String username) {
        return userRepository.findByUsernameWithRoles(username)
                .orElseThrow(() -> new RuntimeException("用戶不存在: " + username));
    }
    /**
     * 根據(jù)ID查找用戶
     */
    public SysUser findById(Long id) {
        return userRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("用戶不存在,ID: " + id));
    }
    /**
     * 查找所有用戶
     */
    public List<SysUser> findAll() {
        return userRepository.findAll();
    }
    /**
     * 創(chuàng)建用戶
     */
    @Transactional
    public SysUser createUser(SysUser user) {
        // 檢查用戶名是否已存在
        if (userRepository.existsByUsername(user.getUsername())) {
            throw new RuntimeException("用戶名已存在: " + user.getUsername());
        }
        // 檢查郵箱是否已存在
        if (user.getEmail() != null && userRepository.existsByEmail(user.getEmail())) {
            throw new RuntimeException("郵箱已存在: " + user.getEmail());
        }
        // 加密密碼
        user.setPassword(passwordEncoder.encode(user.getPassword()));
        user.setCreateTime(new Date());
        user.setUpdateTime(new Date());
        return userRepository.save(user);
    }
    /**
     * 更新用戶
     */
    @Transactional
    public SysUser updateUser(SysUser user) {
        Optional<SysUser> existingUser = userRepository.findById(user.getId());
        if (!existingUser.isPresent()) {
            throw new RuntimeException("用戶不存在,ID: " + user.getId());
        }
        SysUser userToUpdate = existingUser.get();
        // 更新字段
        if (user.getEmail() != null) {
            userToUpdate.setEmail(user.getEmail());
        }
        if (user.getPhone() != null) {
            userToUpdate.setPhone(user.getPhone());
        }
        if (user.getEnabled() != null) {
            userToUpdate.setEnabled(user.getEnabled());
        }
        if (user.getRoles() != null) {
            userToUpdate.setRoles(user.getRoles());
        }
        userToUpdate.setUpdateTime(new Date());
        return userRepository.save(userToUpdate);
    }
    /**
     * 刪除用戶
     */
    @Transactional
    public void deleteUser(Long id) {
        if (!userRepository.existsById(id)) {
            throw new RuntimeException("用戶不存在,ID: " + id);
        }
        userRepository.deleteById(id);
    }
    /**
     * 修改密碼
     */
    @Transactional
    public void changePassword(String username, String oldPassword, String newPassword) {
        SysUser user = findByUsername(username);
        // 驗證舊密碼
        if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
            throw new RuntimeException("舊密碼錯誤");
        }
        // 更新密碼
        user.setPassword(passwordEncoder.encode(newPassword));
        user.setUpdateTime(new Date());
        userRepository.save(user);
        log.info("用戶 {} 修改密碼成功", username);
    }
}

12. 全局異常處理

12.1 全局異常處理器

package com.example.oauth2.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 處理認證異常
     */
    @ExceptionHandler(AuthenticationException.class)
    public ResponseEntity<Map<String, Object>> handleAuthenticationException(AuthenticationException e) {
        log.error("認證異常: {}", e.getMessage());
        Map<String, Object> result = new HashMap<>();
        result.put("code", HttpStatus.UNAUTHORIZED.value());
        result.put("message", "認證失敗: " + e.getMessage());
        result.put("timestamp", System.currentTimeMillis());
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(result);
    }
    /**
     * 處理權限不足異常
     */
    @ExceptionHandler(AccessDeniedException.class)
    public ResponseEntity<Map<String, Object>> handleAccessDeniedException(AccessDeniedException e) {
        log.error("權限不足: {}", e.getMessage());
        Map<String, Object> result = new HashMap<>();
        result.put("code", HttpStatus.FORBIDDEN.value());
        result.put("message", "權限不足: " + e.getMessage());
        result.put("timestamp", System.currentTimeMillis());
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body(result);
    }
    /**
     * 處理業(yè)務異常
     */
    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<Map<String, Object>> handleRuntimeException(RuntimeException e) {
        log.error("業(yè)務異常: {}", e.getMessage());
        Map<String, Object> result = new HashMap<>();
        result.put("code", HttpStatus.BAD_REQUEST.value());
        result.put("message", e.getMessage());
        result.put("timestamp", System.currentTimeMillis());
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(result);
    }
    /**
     * 處理其他異常
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Map<String, Object>> handleException(Exception e) {
        log.error("系統(tǒng)異常: {}", e.getMessage(), e);
        Map<String, Object> result = new HashMap<>();
        result.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
        result.put("message", "系統(tǒng)內(nèi)部錯誤");
        result.put("timestamp", System.currentTimeMillis());
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
    }
}

13. 配置文件

13.1 application.yml

server:
  port: 8080
  servlet:
    context-path: /
spring:
  application:
    name: spring-security-oauth2-demo
  # 數(shù)據(jù)源配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/oauth2_demo?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    username: root
    password: password
  # JPA配置
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect
        format_sql: true
  # Redis配置
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0
    lettuce:
      pool:
        max-active: 8
        max-wait: -1ms
        max-idle: 8
        min-idle: 0
    timeout: 3000ms
# 日志配置
logging:
  level:
    com.example.oauth2: DEBUG
    org.springframework.security: DEBUG
    org.springframework.security.oauth2: DEBUG
# JWT配置
jwt:
  secret: mySecretKey
  expiration: 86400
# 安全配置
security:
  oauth2:
    client:
      client-id: client
      client-secret: secret
    authorization:
      check-token-access: permitAll()

14. 測試數(shù)據(jù)初始化

14.1 數(shù)據(jù)初始化腳本

package com.example.oauth2.config;
import com.example.oauth2.entity.SysRole;
import com.example.oauth2.entity.SysUser;
import com.example.oauth2.repository.RoleRepository;
import com.example.oauth2.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Date;
@Slf4j
@Component
public class DataInitializer implements CommandLineRunner {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private RoleRepository roleRepository;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Override
    public void run(String... args) throws Exception {
        // 初始化角色
        if (roleRepository.count() == 0) {
            SysRole adminRole = new SysRole();
            adminRole.setRoleName("管理員");
            adminRole.setRoleCode("ROLE_ADMIN");
            adminRole.setDescription("系統(tǒng)管理員");
            adminRole.setCreateTime(new Date());
            SysRole userRole = new SysRole();
            userRole.setRoleName("普通用戶");
            userRole.setRoleCode("ROLE_USER");
            userRole.setDescription("普通用戶");
            userRole.setCreateTime(new Date());
            roleRepository.saveAll(Arrays.asList(adminRole, userRole));
            log.info("初始化角色數(shù)據(jù)完成");
        }
        // 初始化管理員用戶
        if (userRepository.count() == 0) {
            SysRole adminRole = roleRepository.findByRoleCode("ROLE_ADMIN")
                    .orElseThrow(() -> new RuntimeException("管理員角色不存在"));
            SysUser adminUser = new SysUser();
            adminUser.setUsername("admin");
            adminUser.setPassword(passwordEncoder.encode("admin123"));
            adminUser.setEmail("admin@example.com");
            adminUser.setPhone("13800000000");
            adminUser.setRoles(Arrays.asList(adminRole));
            adminUser.setCreateTime(new Date());
            adminUser.setUpdateTime(new Date());
            userRepository.save(adminUser);
            log.info("初始化管理員用戶完成,用戶名: admin, 密碼: admin123");
        }
        // 初始化OAuth2客戶端
        // 這里可以通過SQL腳本初始化,也可以在啟動后通過管理界面添加
    }
}

15. 測試與驗證

15.1 獲取訪問令牌

使用密碼模式獲取訪問令牌:

# 獲取訪問令牌
curl -X POST \
  http://localhost:8080/oauth/token \
  -H 'Authorization: Basic Y2xpZW50OnNlY3JldA==' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=password&username=admin&password=admin123&scope=all'

響應示例:

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "expires_in": 3599,
    "scope": "all",
    "user_id": 1,
    "username": "admin",
    "email": "admin@example.com"
}

15.2 訪問受保護資源

# 訪問當前用戶信息
curl -X GET \
  http://localhost:8080/api/user/me \
  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
# 訪問用戶列表(需要ADMIN權限)
curl -X GET \
  http://localhost:8080/api/user/list \
  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
# 訪問公開接口(無需認證)
curl -X GET http://localhost:8080/api/public/info

16. 安全最佳實踐

16.1 安全配置建議

  • 使用HTTPS:在生產(chǎn)環(huán)境中始終使用HTTPS
  • 強密碼策略:實施密碼復雜度要求
  • 令牌過期時間:設置合理的訪問令牌和刷新令牌過期時間
  • 限制重試次數(shù):防止暴力破解
  • 定期更換密鑰:定期更換JWT簽名密鑰

16.2 監(jiān)控與日志

  • 記錄認證日志:記錄所有認證成功和失敗事件
  • 監(jiān)控異常行為:監(jiān)控異常登錄模式
  • 定期審計:定期審計權限分配和令牌使用情況

總結

本文詳細介紹了 Spring Boot 2.0 整合 Spring Security OAuth2 的完整流程,包括:

  • 理論基礎:OAuth2 的核心概念和授權模式
  • 環(huán)境搭建:項目創(chuàng)建和依賴配置
  • 數(shù)據(jù)庫設計:用戶、角色和OAuth2相關表結構
  • 實體類設計:JPA實體和Spring Security集成
  • 安全配置:Spring Security和OAuth2服務器配置
  • JWT集成:JWT令牌的生成和驗證
  • API開發(fā):受保護和公開接口的實現(xiàn)
  • 異常處理:全局異常處理機制
  • 測試驗證:完整的測試流程

到此這篇關于Spring Boot 2.0 整合 Spring Security OAuth2的文章就介紹到這了,更多相關Spring Boot 整合 Spring Security OAuth2內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Spring實現(xiàn)定時任務的幾種方式總結

    Spring實現(xiàn)定時任務的幾種方式總結

    Spring Task 是 Spring 框架提供的一種任務調(diào)度和異步處理的解決方案,可以按照約定的時間自動執(zhí)行某個代碼邏輯它可以幫助開發(fā)者在 Spring 應用中輕松地實現(xiàn)定時任務、異步任務等功能,提高應用的效率和可維護性,需要的朋友可以參考下本文
    2024-07-07
  • 詳解Springboot之整合JDBCTemplate配置多數(shù)據(jù)源

    詳解Springboot之整合JDBCTemplate配置多數(shù)據(jù)源

    這篇文章主要介紹了詳解Springboot之整合JDBCTemplate配置多數(shù)據(jù)源,文中有非常詳細的代碼示例,對正在學習java的小伙伴們有很好的幫助,需要的朋友可以參考下
    2021-04-04
  • Java中Iterator迭代器的簡單理解

    Java中Iterator迭代器的簡單理解

    這篇文章主要介紹了Java中Iterator迭代器的簡單理解,Iterator接口也是Java集合中的一員,但它與Collection、Map接口有所不同,Iterator主要用于迭代訪問Collection中的元素,因此Iterator對象也被稱為迭代器,需要的朋友可以參考下
    2024-01-01
  • Java中常用的設計模式之責任鏈模式詳解

    Java中常用的設計模式之責任鏈模式詳解

    這篇文章主要為大家詳細介紹了Java中常用的設計模式之責任鏈模式,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-02-02
  • SpringBoot三種方法接口返回日期格式化小結

    SpringBoot三種方法接口返回日期格式化小結

    本文介紹了三種在Spring Boot中格式化接口返回日期的方法,包含使用@JsonFormat注解、全局配置JsonConfig、以及在yml文件中配置時區(qū),具有一定的參考價值,感興趣的可以了解一下
    2025-01-01
  • SpringBoot使用Thymeleaf自定義標簽的實例代碼

    SpringBoot使用Thymeleaf自定義標簽的實例代碼

    這篇文章主要介紹了SpringBoot使用Thymeleaf自定義標簽的實例代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • Spring Boot 各種回滾操作實戰(zhàn)教程(自動回滾、手動回滾、部分回滾)

    Spring Boot 各種回滾操作實戰(zhàn)教程(自動回滾、手動回滾、部分回滾)

    這篇文章主要介紹了Spring Boot 各種回滾操作實戰(zhàn)教程(自動回滾、手動回滾、部分回滾),本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-07-07
  • 使用Java實現(xiàn)MySQL數(shù)據(jù)鎖定的策略

    使用Java實現(xiàn)MySQL數(shù)據(jù)鎖定的策略

    在并發(fā)環(huán)境下,多個線程同時對MySQL數(shù)據(jù)庫進行讀寫操作可能會導致數(shù)據(jù)沖突和不一致的問題,為了解決這些并發(fā)沖突,我們可以采用數(shù)據(jù)鎖定策略來保證數(shù)據(jù)的一致性和完整性,下面將介紹如何使用Java實現(xiàn)MySQL數(shù)據(jù)鎖定策略,,需要的朋友可以參考下
    2023-08-08
  • springboot整合redisson實現(xiàn)延時隊列(附倉庫地址)

    springboot整合redisson實現(xiàn)延時隊列(附倉庫地址)

    延時隊列用于管理需要定時執(zhí)行的任務,對于大數(shù)據(jù)量和高實時性需求,使用延時隊列比定時掃庫更高效,Redisson提供一種高效的延時隊列實現(xiàn)方式,本文就來詳細的介紹一下,感興趣都可以了解學習
    2024-10-10
  • jpa?onetomany?使用級連表刪除被維護表數(shù)據(jù)時的坑

    jpa?onetomany?使用級連表刪除被維護表數(shù)據(jù)時的坑

    這篇文章主要介紹了jpa?onetomany?使用級連表刪除被維護表數(shù)據(jù)時的坑,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12

最新評論

肃北| 海晏县| 伊春市| 广东省| 嘉峪关市| 布拖县| 独山县| 若羌县| 维西| 前郭尔| 策勒县| 云南省| 章丘市| 都昌县| 三亚市| 鄂托克旗| 慈利县| 英山县| 建瓯市| 谢通门县| 天祝| 全州县| 石景山区| 阿克| 通山县| 城市| 微山县| 和顺县| 崇文区| 晋州市| 图木舒克市| 乌海市| 安新县| 洱源县| 永寿县| 开江县| 比如县| 中宁县| 南京市| 池州市| 灵石县|