一、基本使用

1.1 添加依赖

	<dependencies>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Spring Security Oauth2 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
    </dependencies>

启动输出:

image-20200908231615611

1.2 登录

username:user
password:4757819a-077e-42bb-a589-0e8322b824f6

image-20200908231722740

1.3 常用配置

WebSecurityConfiguration.java

package com.qiang.security.oauth2.code.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * @author: 吴多强
 * @create: 2020-09-08 23:33
 * @description: 服务器安全配置
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

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

    /**
     * 在内存创建用户
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                // 使用内存存储密码
                .inMemoryAuthentication()
                // 添加一个管理员用户,authorities拥有的资源访问权限
                .withUser("admin").password(passwordEncoder().encode("123456")).roles("ADMIN")
                .and()
                // 添加一个普通用户,authorities拥有的资源访问权限
                .withUser("qiang").password(passwordEncoder().encode("123456")).roles("USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 指定角色有访问的资源权限
                .antMatchers("/resource/one").hasRole("USER")
                .antMatchers("/resource/two").hasRole("ADMIN")
                // 请求为/user/**的需要认证
                .antMatchers("/user/**").authenticated()
                // 请求不为/user/**的不需要认证
                .anyRequest().permitAll()
                .and()
                // 允许表单登录
                .formLogin()
                // 登录成功后跳转的地址
                .successForwardUrl("/user/success");
    }
}

表单参数

protected void configure(HttpSecurity http) throws Exception {
        http
                // 若要给应用程序发送请求,则发送请求的用户必须先通过认证
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                // 允许用户采用表单登录的方式进行认证
                .formLogin()
                // 重写登录页面
                .loginPage("/login")
                // 登录成功后跳转的页面,必须是Post请求的
                .successForwardUrl("/oauth/login/success")
                .and()
                // 允许用户采用HTTP基本的认证方式进行认证
                .httpBasic();
    }

上述代码是默认的配置,规定了一下三点:

  1. 若要给应用程序发送请求,则发送请求的用户必须先通过认证。
  2. 允许用户采用表单登录的方式进行认证。
  3. 允许用户采用HTTP基本的认证方式进行认证。

资源设置

protected void configure(HttpSecurity http) throws Exception {
            http
                    .authorizeRequests()                                                                
                    .antMatchers("/resources/**", "/signup", "/about").permitAll()                 
                    .antMatchers("/admin/**").hasRole("ADMIN")                                      
                    .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")            
                    .anyRequest().authenticated()                                                   
                    .and()
                    // ...
                    .formLogin();
        }
  1. http.authorizeRequests()下添加了多个匹配器,每个匹配器用来控制不同的URL接受不同的用户访问。简单讲,http.authorizeRequests()就是在进行请求的权限配置。
  2. 所有用户都可以访问以/resources/**开头的URL,和/signup/about两个URL。
  3. 拥有ADMIN角色的用户可以访问以/admin/开头的URL。hasRole(String):如果当前用户有String表示的角色,则返回True
  4. 同时拥有ADMINDBA角色的用户可以访问以/db/**开头的URL。access(String):当Stringtrue时才可进行访问。
  5. 所有没被匹配器匹配到的URL都需用户通过认证。
  6. and()返回一个SecurityBuilderSpring Security支持两种认证方式:formLogin()httpBasic()

1.4 常用路径

# 登录
http://localhost:8002/login
# 登出
http://localhost:8002/logout
# 获取token
http://localhost:8002/oauth/token

二、简化模式

2.1 服务器安全配置

package com.qiang.security.oauth2.simple.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;

/**
 * @author: 吴多强
 * @create: 2020-09-08 23:33
 * @description: 服务器安全配置
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

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

    /**
     * 在内存创建用户
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                // 使用内存存储密码
                .inMemoryAuthentication()
                // 添加一个管理员用户,authorities拥有的资源访问权限
                .withUser("admin").password(passwordEncoder().encode("123456")).roles("ADMIN")
                .and()
                // 添加一个普通用户,authorities拥有的资源访问权限
                .withUser("qiang").password(passwordEncoder().encode("123456")).roles("USER");
    }
}

2.2 认证服务器配置

package com.qiang.security.oauth2.simple.config;

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

/**
 * @author: 吴多强
 * @create: 2020-09-10 00:51
 * @description:
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.
                inMemory()
                .withClient("client")
                .secret(new BCryptPasswordEncoder().encode("secret"))
                // 授权模式,implicit简化模式
                .authorizedGrantTypes("authorization_code","implicit")
                .scopes("app")
                .redirectUris("www.wuduoqiang.com");
    }
}

2.3 获取token

访问:

http://localhost:8003/oauth/authorize?response_type=token&client_id=client&redirect_uri=www.wuduoqiang.com

response_type=token 说明是简化模式。

image-20200909212406104

是否授权。

image-20200910014818762

拿到token。

image-20200910014900386

三、授权码模式

3.1 服务器安全配置

package com.qiang.security.oauth2.code.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;

/**
 * @author: 吴多强
 * @create: 2020-09-08 23:33
 * @description: 服务器安全配置
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

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

    /**
     * 在内存创建用户
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                // 使用内存存储密码
                .inMemoryAuthentication()
                // 添加一个管理员用户,authorities拥有的资源访问权限
                .withUser("admin").password(passwordEncoder().encode("123456")).roles("ADMIN")
                .and()
                // 添加一个普通用户,authorities拥有的资源访问权限
                .withUser("qiang").password(passwordEncoder().encode("123456")).roles("USER");
    }
}

3.2 认证服务器配置

package com.qiang.security.oauth2.code.config;

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

/**
 * @author: 吴多强
 * @create: 2020-09-08 23:34
 * @description: 配置认证服务器
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 配置客户端
        clients
                // 使用内存设置
                .inMemory()
                // client_id
                .withClient("client")
                // client_secret
                .secret(passwordEncoder.encode("secret"))
                // 授权类型
                .authorizedGrantTypes("authorization_code")
                // 授权范围
                .scopes("app")
                // 注册回调地址
                .redirectUris("https://www.baidu.com");
    }
}

3.3 获取token

获取授权码。

http://localhost:8002/oauth/authorize?client_id=client&response_type=code

第一次会自动跳转到登录。

http://localhost:8002/login

image-20200909212406104

验证成功后会询问用户是否授权客户端。

image-20200909212624891

选择授权后会得到一个code。

image-20200909212655067

然后使用code获取token。

http://client:secret@localhost:8002/oauth/token?grant_type=authorization_code&code=r2i0JK

image-20200909212826023

四、客户端模式

4.1 认证服务器配置

package com.qiang.security.oauth2.client.config;

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

/**
 * @author: 吴多强
 * @create: 2020-09-09 22:45
 * @description:
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

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

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 配置客户端
        clients
                // 使用内存设置
                .inMemory()
                // client_id
                .withClient("client")
                // client_secret
                .secret(passwordEncoder().encode("secret"))
                // 授权类型
                .authorizedGrantTypes("client_credentials")
                // 授权范围
                .scopes("app")
                // 注册回调地址
                .redirectUris("https://www.baidu.com");
    }
}

4.2 获取token

image-20200910000434975

image-20200910000519990

五、密码模式

5.1 自定义认证授权

实现spring security oauth2 的UserDetailsService接口,自定义认证授权。

package com.qiang.security.oauth2.password.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
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.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

/**
 * @author: 吴多强
 * @create: 2020-09-09 21:37
 * @description: 实现spring security 的用户接口,自定义认证授权
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    /**
     * 根据用户名获取用户的角色、权限等信息
     *
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("用户名:" + username);
        // 当用户名为qiang的时候才构建UserDetails,后面需要从数据库查询
        if ("qiang".equals(username)) {
            return new User(username, passwordEncoder.encode("123456"), AuthorityUtils.commaSeparatedStringToAuthorityList("ADMIN"));
        }
        return null;
    }
}

5.2 服务器安全配置

package com.qiang.security.oauth2.password.config;

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.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * @author: 吴多强
 * @create: 2020-09-09 21:59
 * @description: 认证服务器安全配置
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

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

    /**
     * 使用自定义的用户认证授权
     * @return
     */
    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        return new UserDetailsServiceImpl();
    }

    /**
     * 配置自定义的用户认证授权
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService());
    }

    /**
     * 用于支持 password 模式
     *
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

5.3 认证服务器配置

package com.qiang.security.oauth2.password.config;

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

/**
 * @author: 吴多强
 * @create: 2020-09-09 22:04
 * @description:
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    /**
     * 注入用于支持 password 模式
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 用于支持密码模式
        endpoints.authenticationManager(authenticationManager);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                // 允许客户端访问 /oauth/check_token 检查 token
                .checkTokenAccess("isAuthenticated()")
                .allowFormAuthenticationForClients();
    }

    /**
     * 配置客户端
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                // 使用内存设置
                .inMemory()
                // client_id
                .withClient("client")
                // client_secret
                .secret(passwordEncoder.encode("secret"))
                // 授权类型,密码模式和刷新令牌
                .authorizedGrantTypes("password", "refresh_token")
                // 授权范围
                .scopes("backend")
                // 可以设置对哪些资源有访问权限,不设置则全部资源都可以访问
                .resourceIds("backend-resources")
                // 设置访问令牌的有效期,这里是 1 天
                .accessTokenValiditySeconds(60 * 60 * 24)
                // 设置刷新令牌的有效期,这里是 30 天
                .refreshTokenValiditySeconds(60 * 60 * 24 * 30);
    }
}

5.4 获取token

http://localhost:8003/oauth/token

username:qiang
password:123456
grant_type:password
client_id:client
client_secret:secret

image-20200909223542306

六、常用注解

@EnableGlobalMethodSecurity

全局方法拦截

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)

(1)@EnableGlobalMethodSecurity(securedEnabled=true) ,开启@Secured 注解过滤权限。

(2)@EnableGlobalMethodSecurity(jsr250Enabled=true),开启@RolesAllowed 注解过滤权限 。

(3)@EnableGlobalMethodSecurity(prePostEnabled=true) ,使用表达式时间方法级别的安全性。

​ @PreAuthorize 在方法调用之前,基于表达式的计算结果来限制对方法的访问

@PostAuthorize 允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常

@PostFilter 允许方法调用,但必须按照表达式来过滤方法的结果

@PreFilter 允许方法调用,但必须在进入方法之前过滤输入值

例如下面代码就表示如果用户具有admin角色,就能访问listAllUsers方法,但是如果方法前不加@preAuthorize注解,意味着所有用户都能访问listAllUsers。

@PreAuthorize("hasRole(‘admin‘)")

    @RequestMapping(value = "/user/", method = RequestMethod.GET)

    @ResponseBody

    publicList listAllUsers() {

        ……

    }