Spring Security Oauth2 代码案例

264 阅读3分钟

什么是OAuth2

OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,OAuth2.0是OAuth协议的延续版本。

授权服务器代码

pom

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

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- Spring Security OAuth2 -->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.4.0.RELEASE</version>
        </dependency>
    </dependencies>

启动类

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

UserDetails登录

package com.service;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
        GrantedAuthority grantedAuthority = null;
        if(username.equals("name1")){
            grantedAuthority = new SimpleGrantedAuthority("name1");
        }else if(username.equals("name2")){
            grantedAuthority = new SimpleGrantedAuthority("name2");
        }else if(username.equals("name3")){
            grantedAuthority = new SimpleGrantedAuthority("name3");
        }else if(username.equals("name4")){
            grantedAuthority = new SimpleGrantedAuthority("name4");
        }else if(username.equals("name5")){
            grantedAuthority = new SimpleGrantedAuthority("name5");
        }else if(username.equals("name6")){
            grantedAuthority = new SimpleGrantedAuthority("name6");
        }
        grantedAuthorities.add(grantedAuthority);
        return new User(username, new BCryptPasswordEncoder().encode("123456"), grantedAuthorities);
    }
}

config

WebSecurityConfiguration

package com.config;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Resource
    UserDetailsServiceImpl userDetailsService;
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        // 设置默认的加密方式
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }
    @Override
    public void configure(WebSecurity web) throws Exception {
        // 将 check_token 暴露出去,否则资源服务器访问时报 403 错误
        web.ignoring().antMatchers("/oauth/check_token");
    }
}

AuthorizationServerConfiguration

package com.config;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    // 注入 WebSecurityConfiguration 中配置的 BCryptPasswordEncoder
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                .inMemory()
                .withClient("client")
                // 还需要为 secret 加密
                .secret(passwordEncoder.encode("secret"))
                .authorizedGrantTypes("authorization_code")
                .scopes("app")
               //.autoApprove(true)//自动授权
                .redirectUris("http://localhost:8082/getcode");

    }
}

获取code

get请求

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

获取token

post请求

http://client:secret@localhost:8080/oauth/token?grant_type=authorization_code&code=XYZJNL

也可以在登录之前先启动token获取服务器直接获取

资源服务器代码

pom

同上

启动类

同上

ResourceServerConfiguration

package com.config;
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    @Primary
    @Bean
    public RemoteTokenServices remoteTokenServices() {
        final RemoteTokenServices tokenServices = new RemoteTokenServices();
        //设置授权服务器check_token端点完整地址
        tokenServices.
                setCheckTokenEndpointUrl("http://localhost:8080/oauth/check_token");
        //设置客户端id与secret,注意:client_secret值不能使用passwordEncoder加密!
        tokenServices.setClientId("client");
        tokenServices.setClientSecret("secret");
        return tokenServices;
    }
    @Override
    public void configure(HttpSecurity http) throws Exception {
            http
                .exceptionHandling()
                .and()
                .sessionManagement().sessionCreationPolicy
                    (SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 以下为配置所需保护的资源路径及权限,需要与认证服务器配置的授权部分对应
                .antMatchers("/name1/**").hasAuthority("name1")
                .antMatchers("/name2/**").hasAuthority("name2")
                .antMatchers("/name3/**").hasAuthority("name3")
                .antMatchers("/name4/**").hasAuthority("name4")
                .antMatchers("/name5/**").hasAuthority("name5")
                .antMatchers("/name6/**").hasAuthority("name6");
    }

}

controller

package com.controller;
@RestController
public class UserController {
    @RequestMapping("/name1")
    public String name1(){
        Authentication authentication = 
            SecurityContextHolder.getContext().getAuthentication();
        Object principal = authentication.getPrincipal();
        return "用户名:"+principal.toString();
    }
    @RequestMapping("/name2")
    public String name2(){
        return "name2";
    }
    @RequestMapping("/name3")
    public String name3(){
        return "name3";
    }
    @RequestMapping("/name4")
    public String name4(){
        return "name4";
    }
    @RequestMapping("/name5")
    public String name5(){
        return "name5";
    }
    @RequestMapping("/name6")
    public String name6(){
        return "name6";
    }
}

application.yml

server:
  port: 8081
logging:
  level:
    root: INFO
    org.springframework.web: INFO
    org.springframework.security: INFO
    org.springframework.security.oauth2: INFO

访问资源

get或post都可以

http://localhost:8081/name1?access_token=60de7fb7-6511-4a18-808b-04c3ab6b53c6

也可以在header上加如:

'Authorization': 'e9ca9dcf-12bc-4e25-8831-afd69d4760ec'

获取token服务器代码

pom app同上

package com.controller;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.support.BasicAuthorizationInterceptor;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class TokenController {
    @RequestMapping("/getcode")
    public  String getCode(String code){
        RestTemplate restTemplate = new RestTemplate();
        // 请求地址client:secret@
        String url = "http://localhost:8080/oauth/token";
        restTemplate.getInterceptors().add(new BasicAuthorizationInterceptor("client", "secret"));
        // 请求头设置,x-www-form-urlencoded格式的数据
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        //提交参数设置
        MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
        map.add("grant_type","authorization_code");
        map.add("code",code);
        // 组装请求体
        HttpEntity<MultiValueMap<String, String>> request =
                new HttpEntity<MultiValueMap<String, String>>(map, headers);
        // 发送post请求,并打印结果,以String类型接收响应结果JSON字符串
        String tokenjson = restTemplate.postForObject(url, request, String.class);
        return  tokenjson;
    }
}