前言

        OAuth2是一个开放标准,也是一个授权框架,使应用程序能够访问其它公司提供的资源,允许用户在第三方应用访问存储在其他服务器上的私密资源,而在整个过程不需要提供用户名和密码给到第三方应用,可以通过提供一个令牌(token)实现该功能,采用令牌的方式可以让用户灵活的对第三方应用授权或收回权限。

         Oauth2本身在前后端不分离的情况下,搭建起来比较容易,直接引用pom,然后将对应的配置文件创建好之后,就能后直接使用它本身自带的接口,进行授权认证的整体流程,但是在前后端分离的情况下,流程的整体流转就需要发生改变,自己在网上找了很多例子,都没有明确的标识前后端分离到底该如何实现,方向都有问题。

        本篇将介绍如何在前后端分离的情况下,实现Oauth2的整体流程,首先前后端分离,意思就是后端提供接口,前端通过调用接口获取数据来实现页面数据的刷染以及页面的跳转,所以在此背景下,Oauth2也需要将所有的接口全部提供给前端,由前端完成登录、授权等页面的流程,所以以下主要介绍的就是如果实现接口。

集成Oauth2

1.pom.xml

<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.0.12.RELEASE</version>
</dependency>
<dependency>
    <groupId>net.sourceforge.nekohtml</groupId>
    <artifactId>nekohtml</artifactId>
    <version>1.9.22</version>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity4</artifactId>
    <version>2.1.3.RELEASE</version>
</dependency>

2.代码集成

AuthApprovalHandler
package com.cloudnative.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.approval.Approval;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.ApprovalStoreUserApprovalHandler;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;

import java.util.*;

/**
 * Created by Administrator on 2017/4/4 0004.
 */
public class AuthApprovalHandler extends ApprovalStoreUserApprovalHandler {

    private int approvalExpirySeconds = -1;

    @Autowired
    private ApprovalStore approvalStore;

    public AuthApprovalHandler(JdbcClientDetailsService clientDetailsService, ApprovalStore approvalStore, OAuth2RequestFactory oAuth2RequestFactory) {
        this.setApprovalStore(approvalStore);
        this.setClientDetailsService(clientDetailsService);
        this.setRequestFactory(oAuth2RequestFactory);
    }


    @Override
    public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizationRequest, Authentication userAuthentication) {
        // Get the approved scopes
        Set<String> requestedScopes = authorizationRequest.getScope();
        Set<String> approvedScopes = new HashSet<String>();
        Set<Approval> approvals = new HashSet<Approval>();

        Date expiry = computeExpiry();

        // Store the scopes that have been approved / denied
        Map<String, String> approvalParameters = authorizationRequest.getApprovalParameters();
        for (String requestedScope : requestedScopes) {
            String approvalParameter = OAuth2Utils.SCOPE_PREFIX + requestedScope;
            String value = approvalParameters.get(approvalParameter);
            value = value == null ? "" : value.toLowerCase();
            if ("true".equals(value) || value.startsWith("approve")||value.equals("on")) {
                approvedScopes.add(requestedScope);
                approvals.add(new Approval(userAuthentication.getName(), authorizationRequest.getClientId(),
                        requestedScope, expiry, Approval.ApprovalStatus.APPROVED));
            }
            else {
                approvals.add(new Approval(userAuthentication.getName(), authorizationRequest.getClientId(),
                        requestedScope, expiry, Approval.ApprovalStatus.DENIED));
            }
        }
        approvalStore.addApprovals(approvals);

        boolean approved;
        authorizationRequest.setScope(approvedScopes);
        if (approvedScopes.isEmpty() && !requestedScopes.isEmpty()) {
            approved = false;
        }
        else {
            approved = true;
        }
        authorizationRequest.setApproved(approved);
        return authorizationRequest;
    }

    private Date computeExpiry() {
        Calendar expiresAt = Calendar.getInstance();
        if (approvalExpirySeconds == -1) { // use default of 1 month
            expiresAt.add(Calendar.MONTH, 1);
        }
        else {
            expiresAt.add(Calendar.SECOND, approvalExpirySeconds);
        }
        return expiresAt.getTime();
    }

}

 

AuthorizationServerConfig
package com.cloudnative.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
import org.springframework.security.oauth2.provider.approval.UserApprovalHandler;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;

import javax.annotation.Resource;
import javax.sql.DataSource;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;
    
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients()
                .authenticationEntryPoint(new OAuth2AuthenticationEntryPoint())
                .checkTokenAccess("permitAll()");
    }
    
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService());
    }
    
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.userDetailsService(userDetailsService)
                .userApprovalHandler(userApprovalHandler())
                .approvalStore(approvalStore())
                .authorizationCodeServices(new JdbcAuthorizationCodeServices(getDataSource()))
                .authenticationManager(authenticationManager)
                .tokenStore(this.getJdbcTokenStore());
    }
    
    @Bean
    public ApprovalStore approvalStore() {
        return new JdbcApprovalStore(getDataSource());
    }
    
    @Bean
    public UserApprovalHandler userApprovalHandler() {
        return new AuthApprovalHandler(clientDetailsService(), approvalStore(), oAuth2RequestFactory());
    }
    
    @Bean
    public JdbcClientDetailsService clientDetailsService() {
        return new JdbcClientDetailsService(getDataSource());
    }
    
    @Bean
    public OAuth2RequestFactory oAuth2RequestFactory() {
        return new DefaultOAuth2RequestFactory(clientDetailsService());
    }
    @Bean
    public TokenStore getJdbcTokenStore(){
        TokenStore tokenStore = new JdbcTokenStore(getDataSource());
        return tokenStore;
    }

    public DataSource getDataSource(){
        return dataSource;
    }
}

MyPasswordEncoder

package com.cloudnative.auth.config;

import com.cloudnative.common.core.utils.StringUtils;
import com.cloudnative.common.security.utils.SecurityUtils;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

@Component
public class MyPasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence charSequence) {
        return charSequence.toString();
    }

    @Override
    public boolean matches(CharSequence charSequence, String s) {
        if(StringUtils.isNotEmpty(charSequence.toString()) && StringUtils.isNotEmpty(s)){
            return SecurityUtils.matchesPassword(charSequence.toString(), s);
        }else{
            return false;
        }
    }
}
WebSecurityConfig
package com.cloudnative.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;

/**
 * Created by Administrator on 2017/2/12 0012.
 */
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyPasswordEncoder myPasswordEncoder;

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/js/**","/css/**","/data/**","/img/**","/less/**","/plugins/**","/sass/**","/scss/**","/signOut/**","/client/**","/oauth/login","/oauth/check_token","/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
//                .loginPage("http://10.10.10.57:10001/oauth2_login")
                .disable()
//                .permitAll()
//                .and()
                .logout()
                .logoutUrl("/logout")
                .permitAll();
        http.csrf().disable();
    }


    @Autowired
    protected UserDetailsService userDetailsService;

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

}

UserDetailsServiceImpl
package com.cloudnative.auth.service;

import com.cloudnative.system.api.domain.SysUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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.stereotype.Service;

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

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
	@Autowired
	private Oauth2Service oauth2Service;

	@Override
	public UserDetails loadUserByUsername(String username)  {
		SysUser sysUser= oauth2Service.findByName(username);
		if (sysUser != null) {
			List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
			User userCore=new User(sysUser.getUserName(), sysUser.getPassword(), grantedAuthorities);
			return userCore;
		} else {
			throw new UsernameNotFoundException("用户" + username + "不存在");
		}
	}

}

 以上就是Oauth2的主要配置文件和用户认证的相关代码。

OauthController
package com.cloudnative.auth.controller;

import com.alibaba.fastjson.JSON;
import com.cloudnative.common.core.domain.R;
import com.cloudnative.common.core.utils.IdUtils;
import com.cloudnative.common.core.utils.StringUtils;
import com.cloudnative.common.core.web.domain.AjaxResult;
import com.cloudnative.system.api.RemoteDictDateService;
import com.cloudnative.system.api.RemoteOauth2Service;
import com.cloudnative.system.api.domain.ApiOauthClientDetails;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerEndpointsConfiguration;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.approval.Approval;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.security.Principal;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

@RequestMapping("oauth")
@Controller
@SessionAttributes("authorizationRequest")
public class OauthController extends AuthorizationEndpoint {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private ApprovalStore approvalStore;

    @Autowired
    protected UserDetailsService userDetailsService;

    @Autowired
    AuthenticationManagerBuilder authenticationManagerBuilder;

    @Resource
    private AuthenticationManager authenticationManager;

    @Autowired
    private RemoteOauth2Service remoteOauth2Service;

    @RequestMapping("/confirm_access2")
    @ResponseBody
    public AjaxResult confirmAccess(Map<String, Object> model){
        Principal principal = SecurityContextHolder.getContext().getAuthentication();
        AuthorizationRequest clientAuth = (AuthorizationRequest) model.remove("authorizationRequest");
        ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());

        Map<String, String> scopes = new LinkedHashMap<String, String>();
        for (String scope : clientAuth.getScope()) {
            scopes.put(OAuth2Utils.SCOPE_PREFIX + scope, "false");
        }
        for (Approval approval : approvalStore.getApprovals(principal.getName(), client.getClientId())) {
            if (clientAuth.getScope().contains(approval.getScope())) {
                scopes.put(OAuth2Utils.SCOPE_PREFIX + approval.getScope(),
                        approval.getStatus() == Approval.ApprovalStatus.APPROVED ? "true" : "false");
            }
        }
        model.put("auth_request", clientAuth);
        model.put("client", client);
        model.put("scopes", scopes);
        System.out.println(JSON.toJSONString(model));
        return AjaxResult.success(model);
    }

    @PostMapping("/authorize2")
    @ResponseBody
    public AjaxResult authorizeView(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model, SessionStatus sessionStatus, Principal principal){
        View view = this.approveOrDeny(approvalParameters, model, sessionStatus, principal);
        return AjaxResult.success(view);
    }

    @GetMapping("/authorize2")
    @ResponseBody
    public AjaxResult getAuthorizeView(Map<String, Object> model, @RequestParam Map<String, String> parameters,
                                       SessionStatus sessionStatus, Principal principal){
        ModelAndView view = this.authorize(model, parameters, sessionStatus, principal);
        System.out.println(JSON.toJSONString(view));
        return AjaxResult.success(view);
    }

    @Resource
    private AuthorizationServerEndpointsConfiguration configuration;

    @Override
    public void afterPropertiesSet() throws Exception {
        setAuthorizationCodeServices(new JdbcAuthorizationCodeServices(dataSource));
        setClientDetailsService(this.clientDetailsService);
        setTokenGranter(this.configuration.getEndpointsConfigurer().getTokenGranter());
        setUserApprovalHandler(configuration.getEndpointsConfigurer().getUserApprovalHandler());
        super.afterPropertiesSet();
    }

    @PostMapping("/login")
    @ResponseBody
    public AjaxResult login(@RequestParam Map<String, String> parameters, HttpServletResponse response){
        String username = parameters.get("username");
        String password = parameters.get("password");
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password);
        Authentication authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        SecurityContextHolder.getContext().setAuthentication(authentication);

        Object principal = authentication.getPrincipal();
        System.out.println(principal);
        return AjaxResult.success(principal);
    }

}
OauthController里面就是实现登录和授权接口的代码

/login接口实现了登录

GET /authorize2  实现了打开授权页面

POST /authorize2 实现了确认授权的接口

getToken直接用Oauth2自带的接口就可以了,这里面除非有特殊的要求,所以不需要重写。

这个时候后端代码算是弄完了,这里面着重阐述一下前后端的实现逻辑。

我自己弄了个demo系统,来实现第三方单点登录的业务,点击云平台登录,这个时候是返回获取授权码的url,有前段完成页面跳转。

 

oauth/authorize?response_type=code&scope=read write&client_id=book&redirect_uri=http://www.baidu.com&state=b375bc2b-25f7-4dce-9b36-5f9e2d20bda1

页面调转到第三方登录页面,这个时候,需要前端将下面的参数自己存储到浏览器里面。

response_type=code&scope=read write&client_id=book&redirect_uri=http://www.baidu.com&state=b375bc2b-25f7-4dce-9b36-5f9e2d20bda1

 

 点击登录,调用后端登录接口成功之后,返回前端登录成功的信息,然后前端跳转到授权页面。

 

点击确认,调用后端的确认授权接口,后端接口成功返回信息,前端从返回的信息里面找出重定向的url,此时code已经返回了,调用重定向的url,完成整体逻辑。

我个人总结:

前后端分离的情况下实现Oauth2,后端只需要将接口暴漏出来,页面跳转后端就不能在控制,需要前端自己控制。

后端代码下载地址

https://download.csdn.net/download/liudongyang123/87662511 

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐