spring boot:spring security實現oauth2授權認證(spring boot 2.3.3)


一,oauth2的用途?

1,什么是oauth2?

OAuth2 是一個開放標准,

它允許用戶讓第三方應用訪問該用戶在某一網站上存儲的私密資源(如頭像、照片、視頻等),

在這個過程中無須將用戶名和密碼提供給第三方應用。

實現這一功能是通過提供一個令牌(token),而不是用戶名和密碼來訪問他們存放在特定服務提供者的數據

2,spring 為oauth2提供的官方文檔:

https://projects.spring.io/spring-security-oauth/docs/oauth2.html

 

3,獲取令牌的方式主要有四種,分別是:

 授權碼模式

 簡單模式

 密碼模式

 客戶端模式

  我們這里演示的是密碼模式

  

說明:劉宏締的架構森林是一個專注架構的博客,地址:https://www.cnblogs.com/architectforest

         對應的源碼可以訪問這里獲取: https://github.com/liuhongdi/

說明:作者:劉宏締 郵箱: 371125307@qq.com

 

二,演示項目的相關信息

1,項目地址:

https://github.com/liuhongdi/securityoauth2

 

2,項目功能說明:

        演示了得到token,用token訪問資源等功能

 

3,項目結構:如圖:

 

 

三,配置文件說明

1,pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--oauth2-->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.5.0.RELEASE</version>
        </dependency>
        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--jaxb-->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-core</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>1.1.1</version>
        </dependency>
        <!--mysql mybatis begin-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--fastjson begin-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.73</version>
        </dependency>

 

2,application.properties

#redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=0
spring.redis.password=lhddemo

#mysql
spring.datasource.url=jdbc:mysql://localhost:3306/security?characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=lhddemo
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

#mybatis
mybatis.mapper-locations=classpath:/mapper/*Mapper.xml
mybatis.type-aliases-package=com.example.demo.mapper

#error
server.error.include-stacktrace=always
#log
logging.level.org.springframework.web=trace

 

3,數據庫:

建表sql:

CREATE TABLE `sys_user` (
 `userId` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
 `userName` varchar(100) NOT NULL DEFAULT '' COMMENT '用戶名',
 `password` varchar(100) NOT NULL DEFAULT '' COMMENT '密碼',
 `nickName` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '昵稱',
 PRIMARY KEY (`userId`),
 UNIQUE KEY `userName` (`userName`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用戶表'
INSERT INTO `sys_user` (`userId`, `userName`, `password`, `nickName`) VALUES
(1, 'lhd', '$2a$10$yGcOz3ekNI6Ya67tqQueS.raxyTOedGsv5jh2BwtRrI5/K9QEIPGq', '老劉'),
(2, 'admin', '$2a$10$yGcOz3ekNI6Ya67tqQueS.raxyTOedGsv5jh2BwtRrI5/K9QEIPGq', '管理員'),
(3, 'merchant', '$2a$10$yGcOz3ekNI6Ya67tqQueS.raxyTOedGsv5jh2BwtRrI5/K9QEIPGq', '商戶老張');

說明:3個密碼都是111111,僅供演示使用

CREATE TABLE `sys_user_role` (
 `urId` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
 `userId` int(11) NOT NULL DEFAULT '0' COMMENT '用戶id',
 `roleName` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '角色id',
 PRIMARY KEY (`urId`),
 UNIQUE KEY `userId` (`userId`,`roleName`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用戶角色關聯表'
INSERT INTO `sys_user_role` (`urId`, `userId`, `roleName`) VALUES
(1, 2, 'ADMIN'),
(2, 3, 'MERCHANT');

 

四,java代碼說明

1,WebSecurityConfig.java

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    private final static BCryptPasswordEncoder ENCODER = new BCryptPasswordEncoder();

    @Resource
    private SecUserDetailService secUserDetailService;     //用戶信息類,用來得到UserDetails

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }
    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        return super.userDetailsService();
    }

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/oauth/**")
                .authorizeRequests()
                .antMatchers("/oauth/**").permitAll()
                .and().csrf().disable();
    }

    @Resource
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(secUserDetailService).passwordEncoder(new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return ENCODER.encode(charSequence);
            }
            //密碼匹配,看輸入的密碼經過加密與數據庫中存放的是否一樣
            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return ENCODER.matches(charSequence,s);
            }
        });
    }
}

放開了到授權服務地址的訪問

 

2,AuthorizationServiceConfig.java

@Configuration
@EnableAuthorizationServer
public class AuthorizationServiceConfig extends AuthorizationServerConfigurerAdapter {
    @Resource
    AuthenticationManager authenticationManager;
    @Resource
    RedisConnectionFactory redisConnectionFactory;
    @Resource
    UserDetailsService userDetailsService;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        String clientId = "client_id";
        String clientSecret = "123";
        clients.inMemory()
                //這個好比賬號
                .withClient(clientId)
                //授權同意的類型
                .authorizedGrantTypes("password", "refresh_token")
                //有效時間
                .accessTokenValiditySeconds(1800)
                .refreshTokenValiditySeconds(60 * 60 * 2)
                .resourceIds("rid")
                //作用域,范圍
                .scopes("all")
                //密碼
                .secret(new BCryptPasswordEncoder().encode(clientSecret));
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(new RedisTokenStore(redisConnectionFactory))
                //身份驗證管理
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //允許客戶端表單身份驗證
        security.allowFormAuthenticationForClients();
    }
}

授權服務器的配置,允許client_id這個賬號的訪問

 

3,ResourceServiceConfig.java

@Configuration
@EnableResourceServer
public class ResourceServiceConfig extends ResourceServerConfigurerAdapter {

    @Resource
    private UserAuthenticationEntryPoint userAuthenticationEntryPoint;

    @Resource
    private UserAccessDeniedHandler userAccessDeniedHandler;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("rid");
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                .antMatchers("/home/**").permitAll();

        http.authorizeRequests()
                .antMatchers("/admin/**").hasAnyRole("admin","ADMIN")
                .antMatchers("/user/**").hasRole("user")
                .anyRequest().authenticated();

        //access deny
        http.exceptionHandling().accessDeniedHandler(userAccessDeniedHandler);
        //unauthorized
        http.exceptionHandling().authenticationEntryPoint(userAuthenticationEntryPoint);
    }
}

資源服務器的配置,admin這個url訪問時需要有admin或ADMIN權限

 

4,UserAccessDeniedHandler.java

@Component("UserAccessDeniedHandler")
public class UserAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        //當用戶在沒有授權的情況下訪問受保護的REST資源時,將調用此方法發送403 Forbidden響應
        System.out.println("UserAccessDeniedHandler");
        //response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
        ServletUtil.printRestResult(RestResult.error(ResponseCode.WEB_403));
    }
}

登錄用戶拒絕訪問的處理

 

5,UserAuthenticationEntryPoint.java

@Component
public class UserAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        // 當用戶嘗試訪問安全的REST資源而不提供任何憑據時,將調用此方法發送401 響應
        System.out.println("i am 401");
        ServletUtil.printRestResult(RestResult.error(ResponseCode.WEB_401));
    }
}

匿名用戶拒絕訪問的處理

 

6,SecUser.java

public class SecUser extends User {
    //用戶id
    private int userid;
    //用戶昵稱
    private String nickname;

    public SecUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);
    }

    public SecUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
    }

    public String getNickname() {
        return nickname;
    }
    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public int getUserid() {
        return userid;
    }
    public void setUserid(int userid) {
        this.userid = userid;
    }
}

擴展spring security的user類

 

7,SecUserDetailService.java

@Component("SecUserDetailService")
public class SecUserDetailService implements UserDetailsService{
    @Resource
    private SysUserService sysUserService;
    //從數據庫查詢得到用戶信息
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //查庫
        SysUser oneUser = sysUserService.getOneUserByUsername(s);//數據庫查詢 看用戶是否存在
        String encodedPassword = oneUser.getPassword();
        Collection<GrantedAuthority> collection = new ArrayList<>();//權限集合
        //用戶權限:需要加 ROLE_
        List<String> roles = oneUser.getRoles();
        //System.out.println(roles);
        for (String roleone : roles) {
            GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_"+roleone);
            collection.add(grantedAuthority);
       }
        //增加用戶的userid,nickname
        SecUser user = new SecUser(s,encodedPassword,collection);
        user.setUserid(oneUser.getUserId());
        user.setNickname(oneUser.getNickName());
        return user;
    }
}

通過查詢數據庫得到用戶信息

 

8,其他非關鍵代碼不一一貼出,可以從github訪問

 

五,測試效果

1,得到token:

用postman訪問:

http://127.0.0.1:8080/oauth/token

如圖:

 

發送請求后效果:

 

 此時我們可以使用 access_token訪問資源服務器了

 

2,用postman訪問:

http://127.0.0.1:8080/admin/hello?access_token=GOuq97fKw-O2eo-3yPp7jrTXc4A

說明:此處的access_token是我們上面所生成的字串

返回:

this is admin method

說明訪問成功

 

3,刷新token,用postman訪問:

http://127.0.0.1:8080/oauth/token

如圖:

 

 說明:此處使用的refresh_token是得到token時所返回的

 

4,查看redis中所保存的token?

登錄到redis查看

127.0.0.1:6379> keys *
1) "access_to_refresh:mei0DkuqTAXw7K70tfcJpg43ERY"
2) "refresh_to_access:-aCSypexyqcfJNfdaO3GGlqfSVU"
3) "uname_to_access:client_id:admin"
4) "auth:mei0DkuqTAXw7K70tfcJpg43ERY"
5) "client_id_to_access:client_id"
6) "access:mei0DkuqTAXw7K70tfcJpg43ERY"
7) "refresh_auth:-aCSypexyqcfJNfdaO3GGlqfSVU"
8) "auth_to_access:8d9934986188793067df3115293372b7"
9) "refresh:-aCSypexyqcfJNfdaO3GGlqfSVU"

 

5,如果換成無權限的賬號,是否還能訪問/admin/hello?

這次使用lhd這個賬號:

 

 因為當前賬號沒有被授權,訪問時會報錯:

 

 

六,查看spring boot的版本

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.3.RELEASE)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM