Springboot Security Oauth2 第二篇:构建一个适用于项目的认证服务器

本文在第一篇简单例子的基础上进行了升级,本文例子只做认证授权服务器。使用场景的话:该认证授权服务器可做多个客户端的认证中心,多个客户端都访问该服务获取token,然后通过token访问接口。客户端部分下一篇讲解。
升级部分包括:

  1. 用户信息存在于Mysql中,方便管理用户;
  2. Token存在于Redis中,方便管理token,如果存在于内存中,重启服务token就失效了;

运用技术:springboot2.x、springsecurity5.x、mybatis、redis等

1.pom.xml

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!-- spring-security-oauth2 -->
		<dependency>
			<groupId>org.springframework.security.oauth</groupId>
			<artifactId>spring-security-oauth2</artifactId>
			<version>2.3.4.RELEASE</version>
		</dependency>
		<!--redis -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		<!--mybatis-->
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.2</version>
		</dependency>
		<!--mysql-->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<!-- Druid 数据连接池依赖 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.1.9</version>
		</dependency>
		<!-- lombok插件 -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.4</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>

2.application.yml 配置

server:
  port: 8888
spring:
  redis:
    host: localhost
    port: 6379
    password: 123456
    database: 0
    jedis:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 20
        min-idle: 0
    timeout: 2000
  datasource:
    url: jdbc:mysql://localhost:3307/oauth?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: Wbb2018.
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
  security:
    oauth2:
      client:
        redirectUris: http://localhost:9999/webjars/springfox-swagger-ui/o2c.html,http://localhost:9998/webjars/springfox-swagger-ui/o2c.html  #该配置目前没有用上,会在下一篇客户端中讲解
        clientId: demoClient
        clientSecret: demoSecret
        authorizedGrantTypes: authorization_code,client_credentials, password, refresh_token,implicit
        scopes: read,write
        resourceIds: oauth2-resource
        accessTokenValiditySeconds: 3600
        refreshTokenValiditySeconds: 3600
mybatis:
  configuration:
    map-underscore-to-camel-case: true

3.Sql方面的代码

3.1表结构sql代码。
这里我建了3个表,user表用于存放用户信息(账号,密码),role表用于存放role权限信息,user_permission用于存放用户和权限的绑定信息。


SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(64) DEFAULT NULL,
  `describe` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES ('1', 'USER', '普通用户');
INSERT INTO `role` VALUES ('2', 'ADMIN', '管理员');

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(64) DEFAULT NULL,
  `password` varchar(64) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'user', '123456');
INSERT INTO `user` VALUES ('2', 'admin', '123456');

-- ----------------------------
-- Table structure for user_permission
-- ----------------------------
DROP TABLE IF EXISTS `user_permission`;
CREATE TABLE `user_permission` (
  `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=3 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user_permission
-- ----------------------------
INSERT INTO `user_permission` VALUES ('1', '1', '1');
INSERT INTO `user_permission` VALUES ('2', '2', '2');

3.2.JavaBean

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class RoleDO {

    private Integer id;
    private String name;
    private String describe;
}
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class UserDO {

    private Integer id;
    private String username;
    private String password;
    private String roleName;
}

3.3.Mybatis Mapper ,都比较简单,就不做解释

import com.wbb.security.bean.RoleDO;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

@Repository
public interface RoleMapper {
    @Select("select id,name,describe from role where id = #{id}")
    RoleDO selectById(@Param("id") Integer id);

}
import com.wbb.security.bean.UserDO;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

@Repository
public interface UserMapper {

    @Select("select id,username,password from user where username = #{username}")
    UserDO selectByName(@Param("username") String username);
}
import com.wbb.security.bean.UserPermissionDO;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface UserPermissionMapper {

    @Select("select up.id,up.role_id,up.user_id,r.`name` as role_name from user_permission up " +
            "join user u on up.user_id = u.id " +
            "join role r on up.role_id = r.id where user_id = #{userId}")
    List<UserPermissionDO> selectByUserId(Integer userId);
}

4.配置认证授权服务器,就不详细介绍,需要介绍可以去看上一篇

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
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.approval.UserApprovalHandler;
import org.springframework.security.oauth2.provider.token.TokenStore;

@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    TokenStore tokenStore;
    @Autowired
    private UserApprovalHandler userApprovalHandler;


    @Value("${spring.security.oauth2.client.clientId}")
    private String clientId;
    @Value("${spring.security.oauth2.client.clientSecret}")
    private String clientSecret;
    @Value("${spring.security.oauth2.client.redirectUris}")
    private String[] redirectUris;
    @Value("${spring.security.oauth2.client.authorizedGrantTypes}")
    private String[] authorizedGrantTypes;
    @Value("${spring.security.oauth2.client.resourceIds}")
    private String resourceIds;
    @Value("${spring.security.oauth2.client.scopes}")
    private String[] scopes;
    @Value("${spring.security.oauth2.client.accessTokenValiditySeconds}")
    private int accessTokenValiditySeconds;
    @Value("${spring.security.oauth2.client.refreshTokenValiditySeconds}")
    private int refreshTokenValiditySeconds;
    
    /**
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
       security.realm(resourceIds)
               .tokenKeyAccess("permitAll()")
               .checkTokenAccess("isAuthenticated()")
               .allowFormAuthenticationForClients();
    }
    /**
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient(clientId)
                .secret(new BCryptPasswordEncoder().encode(clientSecret))
                .redirectUris(redirectUris)
                .authorizedGrantTypes(authorizedGrantTypes)
                .scopes(scopes)
                .resourceIds(resourceIds)
                .accessTokenValiditySeconds(accessTokenValiditySeconds)
                .refreshTokenValiditySeconds(refreshTokenValiditySeconds);
    }

    /**
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
      // 这里与上一篇不同的是,增加tokenStore(用户存放token)和userApprovalHandler(用于用户登录)配置
        endpoints.tokenStore(tokenStore).userApprovalHandler(userApprovalHandler)
                endpoints.tokenStore(tokenStore)	
                .userApprovalHandler(userApprovalHandler)
                .authenticationManager(authenticationManager)
                .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST);//获取token允许的访问方式
    }
}

5.配置


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
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;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.TokenApprovalStore;
import org.springframework.security.oauth2.provider.approval.TokenStoreUserApprovalHandler;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
/**
 * Spring Security默认是禁用注解的,要想开启注解, 需要在继承WebSecurityConfigurerAdapter,并在类上加@EnableGlobalMethodSecurity注解
 * 这里使用redis存储token
 */
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    RedisConnectionFactory redisConnectionFactory;
    @Autowired
    private ClientDetailsService clientDetailsService;

	//使用redis来存储token信息
    @Bean
    public TokenStore tokenStore(){
        return new RedisTokenStore(redisConnectionFactory);
    }

    //MyUserDetailsService用于存放用户信息
    @Bean
    public MyUserDetailsService myUserDetailsService(){
        return new MyUserDetailsService();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailsService())
                .passwordEncoder(passwordEncoder());
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/login")
                .and().logout().logoutUrl("/logout").deleteCookies("JSESSIONID").permitAll()
                .and().authorizeRequests()
                .antMatchers("/login.html").permitAll()
                .anyRequest().authenticated()
                .and().csrf().disable();
    }

    /**
     * spring5.x 需要注入一个PasswordEncoder,否则会报这个错There is no PasswordEncoder mapped for the id \"null\"
     * 加了密码加密,所有用到的密码都需要这个加密方法加密
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    /**
     * 需要配置这个支持password模式
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    @Bean
    @Autowired
    public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore){
        TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
        handler.setTokenStore(tokenStore);
        handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
        handler.setClientDetailsService(clientDetailsService);
        return handler;
    }

    @Bean
    @Autowired
    public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {
        TokenApprovalStore store = new TokenApprovalStore();
        store.setTokenStore(tokenStore);
        return store;
    }
}

  1. 编写MyUserDetailsService类,实现UserDetailsService接口,在loadUserByUsername()方法中来做自己的认证登录逻辑。在该方法中先根据用户名去数据库中找该用户,找到该用户后,再去找该用户的权限,最后通过User类的构造方法返回一个User对象。

import com.wbb.security.bean.UserDO;
import com.wbb.security.bean.UserPermissionDO;
import com.wbb.security.mapper.RoleMapper;
import com.wbb.security.mapper.UserMapper;
import com.wbb.security.mapper.UserPermissionMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.ArrayList;
import java.util.List;

public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    PasswordEncoder passwordEncoder;
    @Autowired
    UserMapper userMapper;
    @Autowired
    RoleMapper roleMapper;
    @Autowired
    UserPermissionMapper userPermissionMapper;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("login name:"+username);
        UserDO userDO =userMapper.selectByName(username);
        if(userDO == null){
            throw  new UsernameNotFoundException("用户不存在");
        }
        ArrayList<GrantedAuthority> authorities = new ArrayList<>();
        List<UserPermissionDO> userPermissionDOs = userPermissionMapper.selectByUserId(userDO.getId());
        for(UserPermissionDO userPermissionDO:userPermissionDOs){
            //这个权限牵涉到底层的投票机制,默认是一票制AffirmativeBased:如果有任何一个投票器运行访问,请求将被立刻允许,而不管之前可能有的拒绝决定
            // RoleVoter投票器识别以"ROLE_"为前缀的role,这里配置已ROLE_前缀开头的role
            authorities.add(new SimpleGrantedAuthority("ROLE_"+userPermissionDO.getRoleName()));
        }
        return new User(username,passwordEncoder.encode(userDO.getPassword()),authorities);
    }
}

7.超low登陆页面login.html,在本篇中无用,配合下一篇客户端使用。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/login" method="post">
    <input type="text"  name="username">
    <input type="password" name="password">
    <input type="submit" value="登录">
</form>
</body>
</html>

8.区别于上一篇,由于本文讲解的是认证授权服务端,就不配置ResourceServerConfig。配置完上面几步后,认证服务器就已经完成了。
接下来,启动项目,直接访问http://localhost:8888/oauth/token?grant_type=password&client_id=demoClient&client_secret=demoSecret&username=admin&password=123456,访问成功,返回值也没问题
在这里插入图片描述
换一个user用户,同样没问题
在这里插入图片描述
演示到这里就结束了,因为是认证服务器,本文就不访问接口url了,只演示获取token这部分,url访问放到下一篇客户端中演示。

讲到这里,本篇文章到此也就结束了,如果文章中有问题,或者有一些不够严谨完善的地方,希望大家体谅体谅。欢迎大家留言,交流交流。
最后附上本项目github地址

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OAuth2是一种授权协议,用于授权第三方应用程序访问用户资源。Spring Security一个强大的安全框架,可以与OAuth2协议一起使用来构建安全的应用程序。Spring Boot框架可以轻松地构建基于Spring SecurityOAuth2的认证服务器。 下面是实现Spring Boot OAuth2认证服务器的步骤: 1. 添加Spring SecurityOAuth2依赖项。在pom.xml文件中添加以下依赖项: ```xml <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> <version>2.4.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> ``` 2. 配置Spring Security。创建一个SecurityConfig类,继承WebSecurityConfigurerAdapter,并覆盖configure方法: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/oauth/token").permitAll() .anyRequest().authenticated() .and() .httpBasic(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } ``` 这里定义了一个UserDetailsService来获取用户信息,并使用BCryptPasswordEncoder来加密密码。configure方法定义了认证和授权的规则,这里允许访问/oauth/token路径。 3. 配置OAuth2。创建一个AuthorizationServerConfig类,继承AuthorizationServerConfigurerAdapter,并覆盖configure方法: ```java @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Autowired private DataSource dataSource; @Autowired private PasswordEncoder passwordEncoder; @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.jdbc(dataSource).passwordEncoder(passwordEncoder); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager) .userDetailsService(userDetailsService); } } ``` 这里使用了JDBC存储客户端信息和令牌,使用PasswordEncoder来加密客户端密码。configure方法定义了授权服务器的配置,包括客户端信息、令牌存储方式、身份验证管理器和用户详细信息服务。 4. 配置数据源。在application.properties文件中配置数据源,例如使用MySQL数据库: ``` spring.datasource.url=jdbc:mysql://localhost:3306/oauth2?useSSL=false spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.jdbc.Driver ``` 5. 测试OAuth2认证服务器。启动应用程序并访问/oauth/token路径,可以获取访问令牌。例如: ``` 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=user&password=password' ``` 这里使用了基本身份验证来验证客户端,用户名为client,密码为secret。授权类型为密码,用户名为user,密码为password。成功获取访问令牌后,可以使用该令牌来访问需要授权的资源。 以上就是使用Spring Boot实现OAuth2认证服务器的步骤。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值