springboot+oauth2+redis實現單點登錄

寫這篇博客的時候看了網上很多Oauth2的案例,很多都集成了jwt,但是感覺這個東西實在是沒有必要。

以下是自己參考網上博客,琢磨出來的代碼,有待完善。

github:https://github.com/LI-DAI/mall_demo

mall-admin 模塊

首先貼以下主要依賴

<!--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.3.RELEASE</version>
</dependency>

再看一下配置文件

server.port=2001
spring.application.name=mall-admin
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.url=jdbc:mysql://10.0.93.60:3306/mall_demo?useUnicode=true&characterEncoding=utf8
#spring.datasource.druid.url=jdbc:mysql://localhost:3306/mall_demo?useUnicode=true&characterEncoding=utf8
spring.datasource.druid.username=root
spring.datasource.druid.password=123456
#\u521D\u59CB\u5316\u8FDE\u63A5\u6C60\u5927\u5C0F
spring.datasource.druid.initial-size=1
#\u6700\u5927\u8FDE\u63A5
spring.datasource.druid.max-active=20
#\u7B49\u5F85\u8D85\u65F6\u65F6\u95F4
spring.datasource.druid.max-wait=30000
#\u6700\u5C0F\u8FDE\u63A5
spring.datasource.druid.min-idle=1
#\u9694\u591A\u4E45\u8FDB\u884C\u4E00\u6B21\u68C0\u6D4B \u68C0\u6D4B\u9700\u8981\u5173\u95ED\u7A7A\u95F2\u8FDE\u63A5 \u5355\u4F4D\u6BEB\u79D2
spring.datasource.druid.time-between-eviction-runs-millis=50000
#\u4E00\u4E2A\u8FDE\u63A5\u5728\u6C60\u4E2D\u6700\u5C0F\u7684\u751F\u5B58\u65F6\u95F4 \u5355\u4F4D\u6BEB\u79D2
spring.datasource.druid.min-evictable-idle-time-millis=50000
#\u9A8C\u8BC1\u8FDE\u63A5\u662F\u5426\u6709\u6548
spring.datasource.druid.validation-query=select 1 from dual
#\u5982\u679C\u7A7A\u95F2\u65F6\u95F4\u5927\u4E8EtimeBetweenEvictionRunsMillis\uFF0C\u5219\u6267\u884Cvalidation-query\u68C0\u6D4B\u662F\u5426\u6709\u6548
spring.datasource.druid.test-while-idle=true
#\u7533\u8BF7\u8FDE\u63A5\u65F6\u662F\u5426\u68C0\u6D4B
spring.datasource.druid.test-on-borrow=false
#\u5F52\u8FD8\u8FDE\u63A5\u65F6\u662F\u5426\u68C0\u6D4B
spring.datasource.druid.test-on-return=false
#\u6253\u5F00PsCache
spring.datasource.druid.pool-prepared-statements=true
#\u6307\u5B9APsCache\u5927\u5C0F
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20
#\u914D\u7F6E\u76D1\u63A7\u7EDF\u8BA1\u62E6\u622A\u7684filters \u82E5\u4E0D\u914D\u7F6E \u65E0\u6CD5\u7EDF\u8BA1\u76D1\u63A7\u754C\u9762sql
spring.datasource.druid.filters=stat,wall
#\u5408\u5E76\u591A\u4E2ADruidDataSource\u7684\u76D1\u63A7\u6570\u636E
spring.datasource.druid.use-global-data-source-stat=true
#\u901A\u8FC7connectProperties\u5C5E\u6027\u6765\u6253\u5F00mergeSql\u529F\u80FD\uFF1B\u6162SQL\u8BB0\u5F55
spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#JPA
spring.jpa.database=mysql
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
#redis
spring.redis.database=0
spring.redis.port=6379
spring.redis.host=10.0.93.60
spring.redis.password=
spring.redis.timeout=5000ms
spring.redis.jedis.pool.min-idle=0
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.max-active=8
#\u6587\u4EF6\u4E0A\u4F20\u8DEF\u5F84
upload.locations=d:/test
#security
#spring.security.user.name=root
#spring.security.user.password=123456
auth-server=http://localhost:2001/oauth
security.oauth2.client.client-id=client
security.oauth2.client.client-secret=secret
security.oauth2.client.access-token-uri=${auth-server}/token
security.oauth2.client.user-authorization-uri=${auth-server}/authorize

下面就是重點了

SecurityConfiguration.java


package com.mall.admin.configuration;

import com.mall.admin.entity.Permission;
import com.mall.admin.entity.User;
import com.mall.admin.repository.PermissionRepository;
import com.mall.admin.repository.UserRepository;
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.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.client.RestTemplate;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author lidai
 * @date 2019/7/9 11:06
 * @since
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PermissionRepository permissionRepository;

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

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.userDetailsService(userDetailsService()).passwordEncoder(bCryptPasswordEncoder());
    }


    @Bean
    public UserDetailsService userDetailsService() {
        return username -> {
            User user = userRepository.findByUsername(username);
            if (null == user) {
                throw new IllegalArgumentException("Username : " + username + " not found !");
            }
            List<Permission> permissions = permissionRepository.getPermissionsByUserId(user.getUserId());
            Set<String> perms = permissions.stream().map(Permission::getPerms).collect(Collectors.toSet());
            List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(perms.toArray(new String[]{}));
            return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
        };
    }


    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }


//    @Override
//    protected void configure(HttpSecurity http) throws Exception {
//        http.formLogin()
//                .successHandler((request, response, authentication) -> response.getWriter().write("congratulations ! login success ."))
//                .and()
//                .authorizeRequests()
//                .antMatchers("/login").permitAll()
//                .antMatchers(HttpMethod.OPTIONS).permitAll()
//                .anyRequest().authenticated()
//                .and()
//                .exceptionHandling()
//                .accessDeniedHandler((request, response, accessDeniedException) -> {
//                    response.setCharacterEncoding("UTF-8");
//                    Writer writer = response.getWriter();
//                    writer.write(JSONObject.toJSONString(Result.build().unauthorized()));
//                    writer.flush();
//                });
//
//    }


    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/oauth/check_token", "/user/login");
    }

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

AuthorizationServerConfiguration.java
/*
 * Copyright (C), 2013-2019, 天津大海雲科技有限公司
 */
package com.mall.admin.configuration;

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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import javax.sql.DataSource;

/**
 * @author lidai
 * @date 2019/7/9 14:16
 * <p>
 * 認證服務器
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Autowired
    private UserDetailsService userDetailsService;

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

        //自動從數據庫查數據
//        clients.withClientDetails(detailsService());
        clients.inMemory()
                .withClient("browser")
                .secret(passwordEncoder.encode("secret"))
                .authorizedGrantTypes("password", "authorization_code", "refresh_token")
                .scopes("all");
//               註冊回調地址
//                .redirectUris("http://www.funtl.com");
    }


    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore())
                .authenticationManager(authenticationManager).userDetailsService(userDetailsService);
//        DefaultTokenServices tokenServices = new DefaultTokenServices();
//        tokenServices.setTokenStore(endpoints.getTokenStore());
//        tokenServices.setSupportRefreshToken(true);
//        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
//        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
//        //1小時
//        tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.HOURS.toSeconds(2));
//        //1天
//        tokenServices.setRefreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1));
//        tokenServices.setReuseRefreshToken(false);
//        endpoints.tokenServices(tokenServices);

    }

    @Bean
    public TokenStore tokenStore() {
//        return new JdbcTokenStore(dataSource);
        return new RedisTokenStore(redisConnectionFactory);
    }

//    @Bean
//    public ClientDetailsService detailsService() {
//        return new JdbcClientDetailsService(dataSource);
//    }


    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
    }
}

ResourceServerConfiguration.java
/*
 * Copyright (C), 2013-2019, 天津大海雲科技有限公司
 */
package com.mall.admin.configuration;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
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;

/**
 * @author lidai
 * @date 2019/7/10 15:53
 * @since
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        super.configure(resources);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and()
                //請求權限配置
                .authorizeRequests()
                .antMatchers("/user/login").permitAll()
                .antMatchers("/oauth/*").permitAll()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                .anyRequest().authenticated();
    }
}

配置類就這樣了,代碼寫的不是太好,將就看吧,以後還會不斷完善填充

下面是另一個模塊

mall-market 模塊

這個模塊只有一資源服務器,訪問的時候會直接請求到認證服務器驗證是否登錄

配置文件

server:
  port: 2002

spring:
  application:
    name: mall-market
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://10.0.93.60:3306/mall_demo?useUnicode=true&characterEncoding=utf8
#      url: jdbc:mysql://localhost:3306/mall_demo?useUnicode=true&characterEncoding=utf8
      username: root
      password: 123456
      initial-size: 1
      max-active: 20
      max-wait: 30000
      min-idle: 1
      time-between-eviction-runs-millis: 50000
      min-evictable-idle-time-millis: 50000
      validation-query: select 1 from dual
      test-on-borrow: false
      test-on-return: false
      test-while-idle: true
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
      filter: stat,wall
      use-global-data-source-stat: true
      connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
  jpa:
    database: mysql
    show-sql: true
    hibernate:
      ddl-auto: update
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
  redis:
    database: 0
    port: 6379
    host: 10.0.93.60
    password:
    timeout: 5000ms
    jedis:
      pool:
        max-active: 8
        max-idle: 8
        max-wait: -1ms
        min-idle: 1
#security
auth-server: http://localhost:2001/oauth
security:
  oauth2:
    client:
      client-id: browser
      client-secret: secret
      access-token-uri: ${auth-server}/token
      user-authorization-uri: ${auth-server}/authorize
    resource:
      token-info-uri: ${auth-server}/check_token
ResourceServerConfiguration.java

package com.mall.market.configuration;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
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;

/**
 * @author lidai
 * @date 2019/7/10 15:53
 * @since
 */
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        super.configure(resources);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and()
                //請求權限配置
                .authorizeRequests()
                .antMatchers("/user/login").permitAll()
                .antMatchers("/oauth/*").permitAll()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                .anyRequest().authenticated();
    }
}

postman 測試

可以直接訪問 http://localhost:2001/oauth/token 獲取token

添加Authorization 參數,即爲我們定義再內存中的client與secret

獲取成功之後,我們看看redis中的數據

我使用了兩個用戶進行登錄,分別是user和admin

成功獲取token之後,可以使用此token進行訪問

示例,訪問market模塊下接口

 

這裏需要注意,需要再access_token前加上token類型:bearer

到這裏基本上單點登錄的功能就已經實現,若有錯誤,歡迎指正。  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章