springSecurity+Oauth2.0之授权模式(客户端、密码模式)

SpringSecurity+Oauth2.0之授权模式

 

1: 客户端模式:

          客户端模式(Client Credentials Grant)指客户端以自己的名义而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。

 

maven依赖:

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

 

创建Oauth2.0需要创建三个相关的表,直接使用官方的SQL脚本即可生成(不要修改表名和字段名)

OAuth2 官方的项目中可以找到:https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql

-- ----------------------------
-- Table structure for oauth_access_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_access_token`;
CREATE TABLE `oauth_access_token`  (
  `token_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `token` blob NULL,
  `authentication_id` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `user_name` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authentication` blob NULL,
  `refresh_token` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`authentication_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
 
-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details`  (
  `client_id` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `access_token_validity` int(11) NULL DEFAULT NULL,
  `refresh_token_validity` int(11) NULL DEFAULT NULL,
  `additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
 
-- ----------------------------
-- Table structure for oauth_refresh_token
-- ----------------------------
DROP TABLE IF EXISTS `oauth_refresh_token`;
CREATE TABLE `oauth_refresh_token`  (
  `token_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `token` blob NULL,
  `authentication` blob NULL
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
————————————————
View Code

 

配置yml(这里没用到数据库可以不配置):

server:
  port: 8082


mybatis:
  mapper-locations: classpath*:mapper/*Mapper.xml
  configuration:
      database-id: MySQL
     # 开启驼峰转换
      map-underscore-to-camel-case: true
      # spring boot集成mybatis的方式打印sql
#      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl


spring:
  application:
    name: oauth2-security-8082
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/my-study?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false
      username: root
      password: 123456
      filters: stat
      # 设置最大数据库连接数,设为0为无限制
      maxActive: 20
      # 配置初始化大小、最小、最大
      initialSize: 1
      #  最大等待时间
      maxWait: 60000
      # 始终保留在池中的最小连接数,如果连接验证失败将缩小至此值
      minIdle: 1
      timeBetweenEvictionRunsMillis: 6000
#      连接在池中保持空闲而不被回收的最小时间(毫秒)
      minEvictableIdleTimeMillis: 30000
      validationQuery: select 'x'
#      对池中空闲的连接是否进行验证,验证失败则回收此连接(默认为false)
      testWhileIdle:  true
#       当从连接池中取出一个连接时是否进行验证,若验证失败则从池中删除该连接并尝试取出另一个连接
      testOnBorrow: true
#      当一个连接使用完归还到连接池时是否进行验证
      testOnReturn: false
#      启用游标缓存,这个对数据库的性能提升很大
      poolPreparedStatements: true
#        要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
      maxOpenPreparedStatements: 20
      filter:
        stat:
          log-slow-sql: true
          slow-sql-millis: 2000
View Code

 

配置认证服务器:

/**
 * @Author dw
 * @ClassName AuthorizationServerConfig
 * @Description 配置认证服务器 提供/oauth2/authorize,/oauth2/token,/oauth2/check_token,/oauth2/confirm_access,/oauth2/error
 * @EnableAuthorizationServer // 这个注解告诉 Spring 这个应用是 OAuth2 的授权服务器
 * @Date 2020/4/20 14:47
 * @Version 1.0
 */

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {


    @Autowired
    private DataSource dataSource;


    @Bean
    public TokenStore tokenStore() {
        //使用内存中的 token store
//        return new InMemoryTokenStore();
        //使用Jdbctoken store
        return new JdbcTokenStore(dataSource);
    }

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

    /**
     * @return
     * @Author dw
     * @Description 配置token的过期日期等
     * @Date 2020/4/23 18:32
     * @Param
     */
    @Bean
    @Primary
    public DefaultTokenServices defaultTokenServices() {
        DefaultTokenServices services = new DefaultTokenServices();
        //设置Token 20秒过期
        services.setAccessTokenValiditySeconds(20);
        //设置刷新token的过期时间
        services.setRefreshTokenValiditySeconds(666);
        services.setTokenStore(tokenStore());
        return services;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 配置客户端, 用于client认证
        clients.withClientDetails(clientDetails());
// 第一次使用的时候,需要配置客户端信息,或者手动添加客户端信息到数据库oauth_client_details表中
//        clients.jdbc(dataSource)
//                .withClient("myClient")
//                .secret(new BCryptPasswordEncoder().encode("123456"))
//                .authorizedGrantTypes("password", "refresh_token")//允许授权范围
//                .authorities("ROLE_ADMIN","ROLE_USER")//客户端可以使用的权限
//                .scopes( "read", "write")
//                .accessTokenValiditySeconds(7200)
//                .refreshTokenValiditySeconds(7200);
    }


    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//        endpoints.tokenStore(tokenStore())
        endpoints.tokenServices(defaultTokenServices())
                //接收GET和POST
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);

    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .allowFormAuthenticationForClients();//允许表单登录
    }


}

 如果说是其他模式:

.authorizedGrantTypes("authorization_code","client_credentials","password","refresh_token")//授权方式
"authorization_code" : code模式
"client_credentials": 客户端模式
"password": 密码模式
"implicit": 简化模式

关于设置Token的过期时间如果配置的是从数据库读取,上面配置的Token过期时间将无效,可以在数据库中设置过期时间

配置资源服务器:

/**
 * @Author dw
 * @ClassName ResourceServerConfig
 * @Description 配置资源器
 * @EnableResourceServer 这个类表明了此应用是OAuth2 的资源服务器,此处主要指定了受资源服务器保护的资源链接
 * @Date 2020/4/20 15:03
 * @Version 1.0
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                //匹配需要资源认证路径
                .antMatcher("/**")
                .authorizeRequests()
                //匹配不需要资源认证路径
                .antMatchers("/oauth/**").permitAll()
                .anyRequest().authenticated();
    }

}

 

配置springSecurity:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

// 配置密码的加密方式
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 用来配置拦截保护的请求
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                //匹配需要资源认证路径
                .antMatcher("/**")
                .authorizeRequests()
                //匹配不需要资源认证路径
                .antMatchers("/oauth/**").permitAll()
                .anyRequest().authenticated();
    }

    public static void main(String[] args) {
        System.out.println(new BCryptPasswordEncoder().encode("123456"));
    }

}

 

Controller受保护的资源:

/**
 * @Author dw
 * @ClassName HelloSecurityController
 * @Description
 * @Date 2020/4/14 11:34
 * @Version 1.0
 */
@RestController
public class HelloSecurityController {

    @GetMapping("/admin/hello")
    public String admin() {
        return "hello admin";
    }

    @GetMapping("/user/hello")
    public String user() {
        return "hello user";
    }

    @GetMapping("/db/hello")
    public String db() {
        return "hello db";
    }

    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }

}

注意:这里可能需要你手动配置你的客户端到这张表中

获取token:

(A) 客户端向认证服务器进行身份认证,并要求一个访问令牌。请求的参数如下:
  • grant_type:表示授权类型,必选项,此处的值固定为"client_credentials"
  • client_id:表示客户端的ID,必选项
  • client_secret:客户端的密码,可选项
  • scope:表示申请的权限范围,可选项

 通过Token访问受保护的资源:

 

 2: 密码模式:

这里分成资源服务器与授权服务器,即分离的形式来搭建,而不是资源服务器与授权服务器都在同一个项目中

maven依赖(两个项目中都要添加):

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

其他的依赖,可能项目中用得上:

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

        <!--操作数据库-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!-- MySQL 驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- Druid 数据库连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.22</version>
        </dependency>

        <!-- region MyBatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.1</version>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

 

2.1:首先我们先搭建授权服务:

yml配置:

server:
  port: 8086


mybatis:
  mapper-locations: classpath*:mapper/*Mapper.xml
  configuration:
      database-id: MySQL
     # 开启驼峰转换
      map-underscore-to-camel-case: true
      # spring boot集成mybatis的方式打印sql
#      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl


spring:
  application:
    name: oauth2-security-password-8086
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/my-study?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false
      username: root
      password: 123456
      filters: stat
      # 设置最大数据库连接数,设为0为无限制
      maxActive: 20
      # 配置初始化大小、最小、最大
      initialSize: 1
      #  最大等待时间
      maxWait: 60000
      # 始终保留在池中的最小连接数,如果连接验证失败将缩小至此值
      minIdle: 1
      timeBetweenEvictionRunsMillis: 6000
#      连接在池中保持空闲而不被回收的最小时间(毫秒)
      minEvictableIdleTimeMillis: 30000
      validationQuery: select 'x'
#      对池中空闲的连接是否进行验证,验证失败则回收此连接(默认为false)
      testWhileIdle:  true
#       当从连接池中取出一个连接时是否进行验证,若验证失败则从池中删除该连接并尝试取出另一个连接
      testOnBorrow: true
#      当一个连接使用完归还到连接池时是否进行验证
      testOnReturn: false
#      启用游标缓存,这个对数据库的性能提升很大
      poolPreparedStatements: true
#        要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
      maxOpenPreparedStatements: 20
      filter:
        stat:
          log-slow-sql: true
          slow-sql-millis: 2000

 

创建表:用户、角色、权限、用户角色表、角色权限表。

CREATE TABLE `user` (
  `id` int(64) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(32) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `enable` tinyint(4) DEFAULT NULL,
  `locked` tinyint(4) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;


CREATE TABLE `role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) DEFAULT NULL,
  `description` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;


CREATE TABLE `resources` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `pattern` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;



CREATE TABLE `user_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL,
  `role_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;


CREATE TABLE `role_resource` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_id` int(11) DEFAULT NULL,
  `resource_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

 

创建实体对象:

package com.dw.study.entity;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * (User)实体类
 *
 * @author makejava
 * @since 2020-04-14 13:10:12
 */
public class User implements UserDetails, Serializable {
    private static final long serialVersionUID = 7673225091735750618L;

    private Integer id;

    private String userName;

    private String password;

    private boolean enable;

    private boolean locked;

    private List<Role> userRoles;


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (Role role : userRoles) {
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return authorities;
    }

    @Override
    public String getUsername() {
        return userName;
    }

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

    @Override
    public boolean isAccountNonLocked() {
        return !locked;
    }

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

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

    public void setPassword(String password) {
        this.password = password;
    }

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

    public boolean isEnable() {
        return enable;
    }

    public void setEnable(boolean enable) {
        this.enable = enable;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public boolean isLocked() {
        return locked;
    }

    public void setLocked(boolean locked) {
        this.locked = locked;
    }

    public List<Role> getUserRoles() {
        return userRoles;
    }

    public void setUserRoles(List<Role> userRoles) {
        this.userRoles = userRoles;
    }
}
public class Role implements Serializable {
    private static final long serialVersionUID = 825384782616737527L;
    
    private Integer id;
    
    private String name;
    
    private String description;


    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}
public class Resources implements Serializable {
    private static final long serialVersionUID = -5840903661920488430L;

    private Integer id;
    
    private String pattern;

    private List<Role> roles;


    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getPattern() {
        return pattern;
    }

    public void setPattern(String pattern) {
        this.pattern = pattern;
    }
}
public class UserRole implements Serializable {
    private static final long serialVersionUID = -33173102844662867L;
    
    private Integer id;
    
    private Integer userId;
    
    private Integer roleId;


    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public Integer getRoleId() {
        return roleId;
    }

    public void setRoleId(Integer roleId) {
        this.roleId = roleId;
    }
}

 

创建dao

@Repository
public interface UserMapperDao {

  public User loadUserByUsername(String userName);

  public List<Role> getUserRolesByUid(Integer id);


}

 

创建mapper

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dw.study.dao.ResourceMapperDao">

    <resultMap id="ResourcesMap" type="com.dw.study.entity.Resources">
        <id column="id" property="id"/>
        <result column="pattern" property="pattern"/>
        <collection property="roles" ofType="com.dw.study.entity.Role">
            <id column="roleId" property="id"/>
            <result column="name" property="name"/>
            <result column="description" property="description"/>
        </collection>
    </resultMap>


    <select id="getAllResources" resultMap="ResourcesMap">
        SELECT
         r.*,
         re.id AS roleId,
         re.`name`,
         re.description
        FROM resources AS r
        LEFT JOIN role_resource AS rr  ON r.id = rr.resource_id
        LEFT JOIN role AS re ON re.id = rr.role_id
    </select>
    
</mapper>

 

创建service

@Service
public class MyUserDetailServiceImpl implements UserDetailsService {
    @Autowired
    private UserMapperDao userMapperDao;
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapperDao.loadUserByUsername(username);
        String encodePassword = passwordEncoder.encode(user.getPassword());
        System.out.println("加密后的密码:" + encodePassword);
        user.setPassword(encodePassword);
        if (user == null) {
            throw new UsernameNotFoundException("账户不存在!");
        }
        List<Role> userRoles = userMapperDao.getUserRolesByUid(user.getId());
        user.setUserRoles(userRoles);
        return user;
    }
}

 

核心:配置认证服务器认证类

package com.dw.study.oauth2Config;

import com.dw.study.service.MyUserDetailServiceImpl;
import com.dw.study.webConfig.CustomWebResponseExceptionTranslator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
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.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;

import javax.sql.DataSource;

/**
 * @Author dw
 * @ClassName AuthorizationServerConfig
 * @Description 配置认证服务器 提供/oauth2/authorize,/oauth2/token,/oauth2/check_token,/oauth2/confirm_access,/oauth2/error
 * @EnableAuthorizationServer // 这个注解告诉 Spring 这个应用是 OAuth2 的授权服务器
 * @Date 2020/4/20 14:47
 * @Version 1.0
 */

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {


    @Autowired
    private DataSource dataSource;

    // 认证管理器,认证用户信息
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private MyUserDetailServiceImpl myUserDetailService;

    @Autowired
    private CustomWebResponseExceptionTranslator webResponseExceptionTranslator;

    @Bean
    public TokenStore tokenStore() {
        //使用内存中的 token store
//        return new InMemoryTokenStore();
        //使用Jdbctoken store
        return new JdbcTokenStore(dataSource);
    }

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

    /**
     * @return
     * @Author dw
     * @Description 配置token的过期日期等
     * @Date 2020/4/23 18:32
     * @Param
     */
//    @Bean
//    @Primary
//    public DefaultTokenServices defaultTokenServices() {
//        DefaultTokenServices services = new DefaultTokenServices();
//        // access token有效期2个小时
//        services.setAccessTokenValiditySeconds(60 * 60 * 2);
//        // refresh token有效期30天
//        services.setRefreshTokenValiditySeconds(60 * 60 * 24 * 30);
//        // 支持使用refresh token刷新access token
//        services.setSupportRefreshToken(true);
//        // 允许重复使用refresh token
//        services.setReuseRefreshToken(true);
////        services.setAuthenticationManager(authenticationManager);
//        services.setTokenStore(tokenStore());
//        return services;
//    }


    private PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider() {
        PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider();
        authenticationProvider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper(myUserDetailService));
        return authenticationProvider;
    }


    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 配置客户端, 用于client认证
        clients.withClientDetails(clientDetails());
// 第一次使用的时候,需要配置客户端信息,或者手动添加客户端信息到数据库oauth_client_details表中
//        clients.jdbc(dataSource)
//                .withClient("myClient")
//                .secret(new BCryptPasswordEncoder().encode("123456"))
//                .authorizedGrantTypes("password", "refresh_token")//允许授权范围
//                .authorities("ROLE_ADMIN","ROLE_USER")//客户端可以使用的权限
//                .scopes( "read", "write")
//                .accessTokenValiditySeconds(7200)
//                .refreshTokenValiditySeconds(7200);
    }


    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore())
//        endpoints.tokenServices(defaultTokenServices())
                .authenticationManager(authenticationManager)
                .userDetailsService(myUserDetailService)
                //接收GET和POST
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                .exceptionTranslator(webResponseExceptionTranslator);

    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .allowFormAuthenticationForClients();//允许表单登录
    }


}

如果不配置token的过期时间,默认是12个小时过期

配置资源

package com.dw.study.oauth2Config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

/**
 * @Author dw
 * @ClassName ResourceServerConfig
 * @Description 配置资源器
 * @EnableResourceServer 这个类表明了此应用是OAuth2 的资源服务器,此处主要指定了受资源服务器保护的资源链接
 * @Date 2020/4/20 15:03
 * @Version 1.0
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {


    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                //定义哪些url需要被保护  哪些不需要保护
                .authorizeRequests()
                //定义这两个链接不需要登录可访问
                .antMatchers("/oauth/token" , "oauth/check_token").permitAll()
                //定义所有的都不需要登录  目前是测试需要
//             .antMatchers("/**").permitAll()
                .anyRequest().authenticated() //其他的都需要登录
                //.antMatchers("/sys/**").hasRole("admin")///sys/**下的请求 需要有admin的角色
                .and()
                .formLogin()
//                .loginPage("/login") //如果未登录则跳转登录的页面   这儿可以控制登录成功和登录失败跳转的页面
                .and()
                .csrf().disable();//防止跨站请求  spring security中默认开启
    }

}

 

配置springSecurity

package com.dw.study.securityConfig;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @Author dw
 * @ClassName WebSecurityConfig
 * @Description
 * @Date 2020/4/20 15:07
 * @Version 1.0
 */

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean() ;
    }


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

    /**
     * 配置用户签名服务 主要是user-details 机制,
     *
     * @param auth 签名管理器构造器,用于构建用户具体权限控制,这里交给oauth2的AuthorizationServer去处理
     * @throws Exception
     */
//    @Override
//    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        auth.userDetailsService(myUserService)
//                .passwordEncoder(passwordEncoder());
//    }


    /**
     * 用来配置拦截保护的请求
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //定义哪些url需要被保护  哪些不需要保护
             .authorizeRequests()
                //定义这两个链接不需要登录可访问
             .antMatchers("/oauth/token" , "oauth/check_token").permitAll()
                //定义所有的都不需要登录  目前是测试需要
//             .antMatchers("/**").permitAll()
             .anyRequest().authenticated() //其他的都需要登录
             //.antMatchers("/sys/**").hasRole("admin")///sys/**下的请求 需要有admin的角色
             .and()
             .formLogin()
//                .loginPage("/login") //如果未登录则跳转登录的页面   这儿可以控制登录成功和登录失败跳转的页面
             .and()
             .csrf().disable();//防止跨站请求  spring security中默认开启
    }

}

 

配置全局异常处理类:

package com.dw.study.webConfig;

import org.bouncycastle.asn1.ocsp.ResponseData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.stereotype.Component;

/**
 * @Author dw
 * @Description web全局异常返回处理器
 * @Date 2020/4/24 14:14
 * @Param
 * @return
 */
@Component
public class CustomWebResponseExceptionTranslator extends DefaultWebResponseExceptionTranslator {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomWebResponseExceptionTranslator.class);

    @SuppressWarnings("PMD")
    @Override

public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
    ResponseEntity responseEntity = super.translate(e);
LOGGER.info("statusCode: " + responseEntity.getStatusCodeValue());
OAuth2Exception oAuth2Exception = new OAuth2Exception("出错了!!");
if(responseEntity.getStatusCodeValue() == 401){
oAuth2Exception.addAdditionalInformation("code", "401");
oAuth2Exception.addAdditionalInformation("data", "");
oAuth2Exception.addAdditionalInformation("message", "用户名错误!");
return new ResponseEntity(oAuth2Exception, HttpStatus.UNAUTHORIZED);
}else if(responseEntity.getStatusCodeValue() == 400){
oAuth2Exception.addAdditionalInformation("code", "400");
oAuth2Exception.addAdditionalInformation("data", "");
oAuth2Exception.addAdditionalInformation("message", "用户密码错误!");
return new ResponseEntity(oAuth2Exception, HttpStatus.BAD_REQUEST);
}else {
oAuth2Exception.addAdditionalInformation("code", String.valueOf(responseEntity.getStatusCodeValue()));
oAuth2Exception.addAdditionalInformation("data", "");
oAuth2Exception.addAdditionalInformation("message", "认证异常");
return new ResponseEntity(oAuth2Exception, HttpStatus.UNAUTHORIZED);
}
}
 }

 

创建访问当前登录用户的接口:

package com.dw.study.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;

/**
 * @Author dw
 * @ClassName UserInfoController
 * @Description
 * @Date 2020/4/23 15:06
 * @Version 1.0
 */
@RestController
public class UserInfoController {


    @Value("${server.port}")
    private String serverPort;

    @GetMapping(value = "/userInfo")
    public Principal getUserInfo(Principal principal) {
        System.out.println("通过端口:" + serverPort + "获取用户信息:" + principal);
        return principal;
    }

}

好的到这里认证服务器就配置完了。

 

下面配置资源服务器:

1、pom同上;

 修改yml:

server:
  port: 8087
management:
security:
enabled: false
security:
  oauth2:
    #token检查的授权端url
    authorization:
      check-token-access: http://127.0.0.1:8086/oauth/check_token
    #对应的注册码与注册密码
    client:
      id: oauth2-password
      client-secret: 123456
      userAuthorizationUri: http://127.0.0.1:8086/oauth/authorize
      access-token-uri: http://127.0.0.1:8086/oauth/token
      scope: all
      grant-type: password
#      authentication-scheme: form
    #获得授权端的当前用户信息url
    resource:
      user-info-uri: http://127.0.0.1:8086/userInfo

 

定义资源服务器配置:

package com.dw.study.oauth2Config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.web.cors.CorsConfiguration;

import javax.servlet.http.HttpServletResponse;

/**
 * @Author dw
 * @ClassName ResourceServerConfig
 * @Description 配置资源器
 * @EnableResourceServer 这个类表明了此应用是OAuth2 的资源服务器,此处主要指定了受资源服务器保护的资源链接
 * @EnableGlobalMethodSecurity : 开启三种可以在方法上面加权限控制的注解
 * @Date 2020/4/20 15:03
 * @Version 1.0
 */
@Configuration
@EnableResourceServer
@EnableOAuth2Client
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {


    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                //匹配需要资源认证路径
                .antMatcher("/**")
                .authorizeRequests()
                //匹配不需要资源认证路径
                .antMatchers("/oauth/**").permitAll()
                .anyRequest().authenticated();
    }

}

 

定义获取用户信息的util:

package com.dw.study.Utils;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.OAuth2Authentication;

import java.util.LinkedHashMap;

/**
 * @Author dw
 * @ClassName UserUtil
 * @Description
 * @Date 2020/4/24 17:05
 * @Version 1.0
 */
public class UserUtil {

    private UserUtil (){}

    public static LinkedHashMap getUser(){
        return getUserDetails();
    }

    private static LinkedHashMap getUserDetails() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        OAuth2Authentication oAuth2Authentication = (OAuth2Authentication)authentication;
        LinkedHashMap userDetails = (LinkedHashMap) oAuth2Authentication.getUserAuthentication().getDetails();
        LinkedHashMap principal = (LinkedHashMap) userDetails.get("principal");
        // 这里需要手动传换成User对象
        return principal;
    }

}

最后新建user、role类,同上面一样

 

建controller

@RestController
@Slf4j
public class MyController {

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/get")
    public String get(){
        return "成功获取到资源: port: " + serverPort;
    }

    @GetMapping("/userInfo")
    public Object getUserInfo(){
        LinkedHashMap user = UserUtil.getUser();
        return user;
    }

}

好的,配置完成。下面测试

 1:oauth_client_detais这张表中配置我们的客户端信息

 

 启动两个项目,然后使用postman:

1: 获取token:

 

 2:刷新token获取token:

 

 访问用户信息:

 

 

关于使用HTTP请求头  Authorization,不直接用  client_id 和  client_secret

 客户端发送http请求流程:
服务器发现配置了http auth,于是检查request里面有没有"Authorization"的http header
如果有,则判断Authorization里面的内容是否在用户列表里面,Authorization header 的典型数据为"Authorization: Basic jdhaHY0=",

其中Basic   表示基础认证,

jdhaHY0=   是base64编码的"user:passwd"字符串。

如果没有,或者用户密码不对,则返回http code 401页面给客户端。

base64在线编码j解码工具:http://tool.chinaz.com/Tools/Base64.aspx

 

 

 Header:

 

Authorization   : Basic YnJvd3Nlcjo=

 

 配置跨域访问问题:

/**
 * 跨域访问配置
 */
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CORSFilter implements Filter {

    public static final String CREDENTIALS_NAME = "Access-Control-Allow-Credentials";
    public static final String ORIGIN_NAME = "Access-Control-Allow-Origin";
    public static final String METHODS_NAME = "Access-Control-Allow-Methods";
    public static final String HEADERS_NAME = "Access-Control-Allow-Headers";
    public static final String MAX_AGE_NAME = "Access-Control-Max-Age";
    @Override
    public void destroy() {

    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) resp;
        HttpServletRequest request = (HttpServletRequest) req;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN");

        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            chain.doFilter(req, resp);
        }

    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }
}

 

 

 

 

posted @ 2020-04-21 17:07  邓维-java  阅读(2685)  评论(0编辑  收藏  举报