Spring Boot + Spring Security + OAuth2 搭建 (一)

源代码地址:https://download.csdn.net/download/wllovar/10963011

具体那里有疑问可以给我发邮件[email protected]

最近应项目需求想搞一些类似微信公众平台那样的提供第三方访问的API,这就要用到OAuth2,具体OAuth2的认证流程如下图所示

Spring Boot + Spring Security + OAuth2 搭建 (一)_第1张图片

好了废话不多说,我们开始干活

1.打开我们新建的SpringBoot项目,打开pom.xml,引入maven依赖包



	4.0.0
	
		org.springframework.boot
		spring-boot-starter-parent
		2.1.1.RELEASE
		 
	
	com.pdl
	springboot
	0.0.1-SNAPSHOT
	SpringBoot
	Demo project for Spring Boot

	
		1.8
	

	
		
			org.springframework.boot
			spring-boot-starter-web
		

		
			org.springframework.boot
			spring-boot-starter-test
			test
		

		
		
			org.postgresql
			postgresql
			runtime
		
		
		
			mysql
			mysql-connector-java
			runtime
		
		
		
			org.springframework.boot
			spring-boot-starter-jdbc
		
		
		
			org.mybatis.spring.boot
			mybatis-spring-boot-starter
			1.3.2
		
		
		
			org.springframework.boot
			spring-boot-starter-aop
		

		
		
			com.alibaba
			druid-spring-boot-starter
			1.1.9
		
		
		
			com.github.pagehelper
			pagehelper-spring-boot-starter
			1.2.5
		
		
		
			com.fasterxml.jackson.core
			jackson-core
		
		
			com.fasterxml.jackson.core
			jackson-databind
		
		
			com.fasterxml.jackson.datatype
			jackson-datatype-joda
		
		
			com.fasterxml.jackson.module
			jackson-module-parameter-names
		
		
			org.springframework.boot
			spring-boot-starter-jta-atomikos
		
		
			org.springframework.boot
			spring-boot-starter-redis
			1.4.7.RELEASE
		
		
		
			org.springframework.boot
			spring-boot-starter-security
		
		
			org.springframework.security.oauth.boot
			spring-security-oauth2-autoconfigure
			2.0.3.RELEASE
		
		
		
			org.springframework.boot
			spring-boot-starter-data-redis
		

		
			commons-lang
			commons-lang
			2.6
		
		
			org.projectlombok
			lombok
			true
		

		
		
			com.baomidou
			mybatis-plus-boot-starter
			3.0.4
		

		
		
			io.springfox
			springfox-swagger2
			2.9.2
		

		
			io.springfox
			springfox-swagger-ui
			2.9.2
		
		
			org.springframework.boot
			spring-boot-starter-thymeleaf
		
		
		 
	

	
		
			
				org.springframework.boot
				spring-boot-maven-plugin
			
		
	


2.在config目录下新建认证服务端配置AuthorizationServerConfiguration.java

package com.pdl.config;


import com.pdl.security.BootClientDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;


@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private BootClientDetailsService clientDetailsService;


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

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

        clients.withClientDetails(clientDetailsService);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                // tonken 存储于内存中
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)// add get method
                .tokenStore(new InMemoryTokenStore())
                .authenticationManager(authenticationManager);
    }

}

3.在config目录下新建认证资源配置ResourceServerConfiguration.java

package com.pdl.config;

import org.springframework.context.annotation.Configuration;
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;

@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
                .authorizeRequests()
                .antMatchers("/register").permitAll()
                .antMatchers("/druid/*").permitAll()
                .anyRequest()
                .authenticated();
    }

}

注:如果需要开放某个接口不许认证操作时应该在ResourceServerConfiguration放开该方法的认证,具体实现是

.antMatchers("/register").permitAll()

如果不加这句话访问/register会一直报

{
    "error": "unauthorized",
    "error_description": "Full authentication is required to access this resource"
}

这个坑坑我了一天,解决后特此标注一下,以便小伙伴们不再趟坑

4.在config目录下新建WebSecurity 配置

package com.pdl.config;


import com.pdl.security.BootUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * Created by wangle on 2018/12/28.
 */
@Configuration
@EnableWebSecurity
@Order(1)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private BootUserDetailService userDetailService;


    /**
     * 让Security 忽略这些url,不做拦截处理
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers
                ("/swagger-ui.html/**", "/webjars/**",
                        "/swagger-resources/**", "/v2/api-docs/**",
                        "/swagger-resources/configuration/ui/**", "/swagger-resources/configuration/security/**",
                        "/images/**");
    }

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

        http
                .formLogin().and()
                .requestMatchers()
                .antMatchers("/login","/oauth/authorize")
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated();

        http.httpBasic().disable();
    }

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


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

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



}

5.自定义自定义 UserDetailsService 和 BootClientDetailsService

package com.pdl.security;

import com.pdl.domain.User;
import com.pdl.service.AdminService;
import com.pdl.service.UserService;
import org.apache.commons.lang.StringUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

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.Component;

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

@Component
public class BootUserDetailService implements UserDetailsService {

    @Autowired
    private UserService userService;

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User user= this.userService.selectUserByUsername(username);
        if(user==null) {
            throw new UsernameNotFoundException("用户名不存在");
        }
        GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER");

        List authorities = new ArrayList<>();
        authorities.add(authority);
        user.setAuthorities(authorities);
        return user;
    }
}

新建类 BootClientDetailsService 实现ClientDetailsService 接口,覆盖loadClientByClientId(String clientId)方法,将其声明为spring组件,方便后面配置使用

package com.pdl.security;

import com.pdl.domain.Client;
import com.pdl.service.AdminService;
import com.pdl.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.stereotype.Component;

@Component
public final class BootClientDetailsService implements ClientDetailsService {

    @Autowired
    private UserService userService;

    @Override
    public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {

        Client client = userService.selectClientByClientId(clientId);

        if(client==null){
            throw new ClientRegistrationException("客户端不存在");
        }

        return new BootClientDetails(client);
    }

}
package com.pdl.security;


import com.pdl.domain.Client;
import com.pdl.util.CommonUtils;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.provider.ClientDetails;

import java.util.Collection;
import java.util.Map;
import java.util.Set;

/**
 * @author wangle
 * @date 2018/10/16  15:36
 *
 **/
@Data
@SuppressWarnings("unchecked")
public final class BootClientDetails implements ClientDetails {

    private Client client;
    private Set scope;

    public BootClientDetails(Client client) {
        this.client = client;
    }

    public BootClientDetails() {
    }

    @Override
    public String getClientId() {
        return client.getClientId();
    }

    @Override
    public Set getResourceIds() {
        return client.getResourceIds()!=null?
                CommonUtils.transformStringToSet(client.getResourceIds(),String.class):null;
    }

    @Override
    public boolean isSecretRequired() {
        return client.getIsSecretRequired();
    }

    @Override
    public String getClientSecret() {
        return client.getClientSecret();
    }

    @Override
    public boolean isScoped() {
        return client.getIsScoped();
    }

    @Override
    public Set getScope() {

        this.scope = client.getScope()!=null?
                CommonUtils.transformStringToSet(client.getScope(),String.class):null;

        return client.getScope()!=null?
                CommonUtils.transformStringToSet(client.getScope(),String.class):null;
    }

    @Override
    public Set getAuthorizedGrantTypes() {
        return client.getAuthorizedGrantTypes()!=null?
                CommonUtils.transformStringToSet(client.getAuthorizedGrantTypes(),String.class):null;
    }

    @Override
    public Set getRegisteredRedirectUri() {
        return client.getRegisteredRedirectUri()!=null?
                CommonUtils.transformStringToSet(client.getRegisteredRedirectUri(),String.class):null;
    }

    @Override
    public Collection getAuthorities() {
        return (client.getAuthorities()!=null&&client.getAuthorities().trim().length()>0)?
                AuthorityUtils.commaSeparatedStringToAuthorityList(client.getAuthorities()):null;
    }

    @Override
    public Integer getAccessTokenValiditySeconds() {
        return client.getAccessTokenValiditySeconds();
    }

    @Override
    public Integer getRefreshTokenValiditySeconds() {
        return client.getRefreshTokenValiditySeconds();
    }

    @Override
    public boolean isAutoApprove(String scope) {
        return  this.client.getIsAutoApprove()==null ? false: this
                .client.getIsAutoApprove();
    }

    @Override
    public Map getAdditionalInformation() {
        return null;
    }
}

6.在DAO层新建UserMapper.java

package com.pdl.dao.crm;

import com.pdl.domain.Client;
import com.pdl.domain.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.HashMap;
import java.util.List;

@Mapper
public interface UserMapper {

    List selectAllUser();

    void insert(HashMap params);

    User selectUserByUserName( @Param("username") String username);

    Client selectClientByClientId(@Param("clientId") String clientId);

    int insertClient(Client client);


}

7.在domain层新建实体类Client.java和User.java

package com.pdl.domain;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import javax.validation.constraints.NotNull;

/**
 * @author wangle
 * @date  2018/10/16  9:23
 *
 **/
@Data
@TableName("clients")
public class Client  {

    @TableId
    private String id;
    @TableField("clientId")
    @NotNull
    private String clientId;
    @TableField("resourceIds")
    private String resourceIds;
    @TableField("isSecretRequired")
    private Boolean isSecretRequired;
    @TableField("clientSecret")
    @NotNull
    private String clientSecret;
    @TableField("isScoped")
    private Boolean isScoped;
    @TableField("scope")
    private String scope;
    @TableField("authorizedGrantTypes")
    @NotNull
    private String authorizedGrantTypes;
    @TableField("registeredRedirectUri")
    @NotNull
    private String registeredRedirectUri;
    @TableField("authorities")
    private String authorities;
    @TableField("isAutoApprove")
    private Boolean isAutoApprove;
    @TableField("accessTokenValiditySeconds")
    @NotNull
    private Integer accessTokenValiditySeconds;
    @TableField("refreshTokenValiditySeconds")
    @NotNull
    private Integer refreshTokenValiditySeconds;
    @TableField("createTime")
    @NotNull
    private String createTime;
    @TableField("modifyTime")
    @NotNull
    private String modifyTime;



}
package com.pdl.domain;


import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

/**
 * @author wangle
 * @date 2018/10/9  15:43
 *
 **/
@Data
@TableName("user")
public class User implements UserDetails {
    @TableId
    private String id;
    private String username;
    private String email;
    @TableField("isEnable")
    private Boolean isEnable;
    @TableField("isExpired")
    private Boolean isExpired;
    @TableField("isLocked")
    private Boolean isLocked;
    private String password;
    private String gender;

    @TableField(exist = false)
    private List authorities;

    @Override
    public List getAuthorities() {
        return authorities;
    }

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

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

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

    @Override
    public boolean isAccountNonLocked() {
        return this.isLocked;
    }

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

    @Override
    public boolean isEnabled() {
        return this.isEnable;
    }
}

8.在service层新建UserService类

package com.pdl.service;

import com.pdl.dao.crm.UserMapper;
import com.pdl.domain.Client;
import com.pdl.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public Map selectUser(){
        Map data = new HashMap();
        List userList = userMapper.selectAllUser();
        data.put("recordList",userList);
        data.put("total",userList.size());
        return data;
    }



    public User selectUserByUsername(String username){
        User user = userMapper.selectUserByUserName(username);
        return user;
    }


    public Client selectClientByClientId(String clientId){
        Client client = userMapper.selectClientByClientId(clientId);
        return client;
    }

    public boolean addClient(Client client){
        boolean flag = false;
        int res = userMapper.insertClient(client);
        if(res==1){
            flag = true;
        }
        return flag;
    }



}

 

9.在controller层新建ClientController.java

package com.pdl.controller;

import com.pdl.domain.Client;
import com.pdl.security.response.BaseResponse;
import com.pdl.security.response.HttpResponse;
import com.pdl.service.UserService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import java.util.UUID;

@RestController
public class ClientController {

    @Autowired
    private UserService userService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @PostMapping("/register")
    public BaseResponse clientRegistered(@RequestBody @Valid Client client){
        client.setId(UUID.randomUUID().toString());
        client.setClientSecret(passwordEncoder.encode(client.getClientSecret()));
        boolean i = userService.addClient(client);
        return HttpResponse.baseResponse(200);
    }

}

10.配置application.yml文件

server:
  port: 8060
spring:
  datasource:
    druid:
      crm:
        #配置监控统计拦截的filters,去掉后监控界面SQL无法进行统计,'wall'用于防火墙
        filters: stat,wall
        driver-class-name:  com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/pdl_oauth?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=GMT%2B8
        username: root
        password: 123456
        #初始化大小
        initial-size: 50
        #最小连接数
        min-idle: 50
        #最大连接数
        max-active: 200
        #获取连接等待超时时间
        max-wait: 60000
        #间隔多久才进行一次检测,检测需要关闭的空闲连接,单位毫秒
        time-between-eviction-runs-millis: 60000
        #一个连接在池中最小生存的时间,单位是毫秒
        min-evictable-idle-time-millis: 30000
        #测试语句是否执行正确
        validation-query: SELECT 'x'
        #指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.
        test-while-idle: true
        #借出连接时不要测试,否则很影响性能
        test-on-borrow: false
        test-on-return: false
        #打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
        pool-prepared-statements: false
        #与Oracle数据库PSCache有关,再druid下可以设置的比较高
        max-pool-prepared-statement-per-connection-size: 20
      #数据源2
      hr:
        filters: stat,wall
        driver-class-name: org.postgresql.Driver
        url: jdbc:postgresql://localhost:5432/pdl_hr?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
        username: postgres
        password: 123456
        initial-size: 50
        min-idle: 50
        max-active: 200
        max-wait: 60000
        time-between-eviction-runs-millis: 60000
        min-evictable-idle-time-millis: 30000
        validation-query: SELECT 'x'
        test-while-idle: true
        test-on-borrow: false
        test-on-return: false
        pool-prepared-statements: false
        max-pool-prepared-statement-per-connection-size: 20
  redis:
    host: 127.0.0.1
    port: 6379
    jedis:
      pool:
        max-active: 8
        max-wait: -1s
        min-idle: 0
        max-idle: 8
  security:









到此为止我们的项目搭建工作已经完成

访问http://localhost:8060/oauth/authorize?response_type=code&client_id=lovar&redirect_uri=http://www.baidu.com&scope=all

Spring Boot + Spring Security + OAuth2 搭建 (一)_第2张图片

输入账号:admin ,密码:123qwe,进行登录进入授权页面

Spring Boot + Spring Security + OAuth2 搭建 (一)_第3张图片

授权完成后会获得授权码https://www.baidu.com/?code=OJN1hB

接下来在postman上进行操作

Spring Boot + Spring Security + OAuth2 搭建 (一)_第4张图片

授权码的操作已完成,下面我们开始测试注册客户端,打开http://localhost:8060/swagger-ui.html

Spring Boot + Spring Security + OAuth2 搭建 (一)_第5张图片

访问/register接口进行客户端注册

{
  "accessTokenValiditySeconds": 1800,
  "authorities": "ADMIN",
  "authorizedGrantTypes": "refresh_token,authorization_code",
  "clientId": "lovar",
  "clientSecret": "123456",
  "isAutoApprove": false,
  "isSecretRequired": true,
  "refreshTokenValiditySeconds": 3600,
  "registeredRedirectUri": "http://localhost:8060",
  "scope": "all",
  "scoped": true,
  "secretRequired": true,
  "createTime": "2019-02-18 00:12:10",
  "modifyTime": "2019-02-18 00:12:10",
  "resourceIds": "boot-server"
}

 

 

你可能感兴趣的:(Spring,Boot)