springcloud alibaba + 前后端分离 实现autho2 认证
OAuth2是一个开放标准,也是一个授权框架,使应用程序能够访问其它公司提供的资源,允许用户在第三方应用访问存储在其他服务器上的私密资源,而在整个过程不需要提供用户名和密码给到第三方应用,可以通过提供一个令牌(token)实现该功能,采用令牌的方式可以让用户灵活的对第三方应用授权或收回权限。
前言
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,后端只需要将接口暴漏出来,页面跳转后端就不能在控制,需要前端自己控制。
后端代码下载地址
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)