前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringCloud微服务实战系列(十八)Ouath2在真实场景中的应用之授权服务器

SpringCloud微服务实战系列(十八)Ouath2在真实场景中的应用之授权服务器

作者头像
品茗IT
发布2020-05-28 16:28:36
1.3K0
发布2020-05-28 16:28:36
举报
文章被收录于专栏:品茗IT品茗IT

SpringCloud微服务实战系列(十八)Ouath2在真实场景中的应用之授权服务器

一、概述

《SpringCloud微服务实战系列(十七)Ouath2在真实场景中的应用之资源服务器》]中

已经介绍了资源服务器是如何搭建的。

本篇就是对Oauth2的实际应用方法的授权服务器搭建方式进行介绍。

在Spring Oauth2中,Oauth2的使用过程中将角色分为三种:ResourceServer,AuthorizationServer,OauthClient.

由于篇幅较大,这里将Oauth2的搭建分成三个部分。本篇先介绍AuthorizationServer。

二、授权服务器

授权服务器就是Oauth2的认证中心。管理token、client等,需要配置的东西很多。

下面讲述下这个过程是怎样的。

2.1 引入依赖

需要引入spring-boot-starter-web、spring-cloud-starter-security和spring-security-oauth2和mybatis相关jar包.

依赖如下:

代码语言:javascript
复制
<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-security</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.security.oauth</groupId>
		<artifactId>spring-security-oauth2</artifactId>
		<version>${security.oauth2.version}</version>
	</dependency>
	<dependency>
		<groupId>org.mybatis.spring.boot</groupId>
		<artifactId>mybatis-spring-boot-starter</artifactId>
	</dependency>
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.34</version>
	</dependency>
	<dependency>
		<groupId>org.apache.commons</groupId>
		<artifactId>commons-dbcp2</artifactId>
	</dependency>
</dependencies>

这里,security.oauth2.version我选择的是2.3.5.RELEASE。

2.2 配置文件

这里使用yaml文件写配置,配置文件application.yml:

application.yml:

代码语言:javascript
复制
server:
   port: 8182
spring:
   main:
      allow-bean-definition-overriding: true
   profiles:
      active: loc
   application:
      name: oauthAuth
mybatis:
   configuration:
      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

这里

  1. 应用名称为oauthAuth
  2. allow-bean-definition-overriding为true,是因为要定义新的bean,覆盖默认的。

application-loc.yml:

代码语言:javascript
复制
env: loc
spring:
   datasource:
      type: org.apache.commons.dbcp2.BasicDataSource
      dbcp2:
         max-wait-millis: 60000
         min-idle: 20
         initial-size: 2
         connection-properties: characterEncoding=utf8
         validation-query: SELECT 1
         test-while-idle: true
         test-on-borrow: true
         test-on-return: false
      driverClassName: com.mysql.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/boot?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
      username: cff
      password: 123456

这里配置的是数据库及数据源信息

2.3 启动

使用main直接启动即可。需要使用注解@EnableAuthorizationServer表明它是授权服务器。

OauthAuthApplication:

代码语言:javascript
复制
package cn.pomit.springbootwork.oauthauth;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;


@EnableAuthorizationServer
@SpringBootApplication
public class OauthAuthApplication {
	public static void main(String[] args) {
		SpringApplication.run(OauthAuthApplication.class, args);
	}
}
2.4 安全控制配置

这个安全控制,只是普通的Spring Security的安全配置。

需要继承WebSecurityConfigurerAdapter,并加上@EnableWebSecurity。

AccessSecurityConfiguration如下:

代码语言:javascript
复制
package cn.pomit.springbootwork.oauthauth.config;

import org.springframework.beans.factory.annotation.Autowired;
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 cn.pomit.springbootwork.oauthauth.handler.AjaxAuthFailHandler;
import cn.pomit.springbootwork.oauthauth.handler.AjaxLogoutSuccessHandler;
import cn.pomit.springbootwork.oauthauth.handler.AuthSuccessHandler;
import cn.pomit.springbootwork.oauthauth.handler.UnauthorizedEntryPoint;
import cn.pomit.springbootwork.oauthauth.provider.SimpleAuthenticationProvider;

@Configuration
@EnableWebSecurity
public class AccessSecurityConfiguration extends WebSecurityConfigurerAdapter {
	@Autowired
	private SimpleAuthenticationProvider simpleAuthenticationProvider;

	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
		auth.authenticationProvider(simpleAuthenticationProvider);
	}

	@Override
	public void configure(HttpSecurity http) throws Exception {
		http.exceptionHandling()
				.authenticationEntryPoint(new UnauthorizedEntryPoint()).and()
				.csrf().disable()
				.authorizeRequests().antMatchers("/oauth/**").permitAll()
				.antMatchers("/").permitAll()
				.antMatchers("/index.html").permitAll()
				.antMatchers("/css/**").permitAll()
				.antMatchers("/js/**").permitAll()
				.antMatchers("/img/**").permitAll()
				.antMatchers("/api/**").authenticated()
				.and().formLogin()
				.loginPage("/login.html").usernameParameter("userName").passwordParameter("userPwd")
				.loginProcessingUrl("/login").successHandler(new AuthSuccessHandler())
				.failureHandler(new AjaxAuthFailHandler()).and()
				.logout().logoutUrl("/logout")
				.logoutSuccessHandler(new AjaxLogoutSuccessHandler());
	}
}

其中,SimpleAuthenticationProvider是处理用户名密码校验的。

UnauthorizedEntryPoint、AuthSuccessHandler、AjaxAuthFailHandler、AjaxLogoutSuccessHandler这些是对认证成功、失败等的处理方法,后面会贴出来,这里先不细讲了。

注意: /oauth/**路径要放开

2.5 授权服务器配置
  1. 因为授权服务器默认存储的客户端信息默认都是基于内存的,然而,实际应用中都是基于数据库的,因此,我们要把它改造为数据库中获取,比如:
  2. Oauth2的授权码是6位,实在是有点短,可以重写AuthorizationCodeServices,将它变成更长位数。
  3. PasswordEncoder需要指定,如果不需要对密码进行处理,可以使用NoOpPasswordEncoder。

AuthorizationServerConfiguration:

代码语言:javascript
复制
package cn.pomit.springbootwork.oauthauth.config;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
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.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;

import cn.pomit.springbootwork.oauthauth.domain.OauthClientInfo;
import cn.pomit.springbootwork.oauthauth.service.OauthClientService;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
	@Autowired
	@Qualifier("authorizationCodeServices")
	private AuthorizationCodeServices authorizationCodeServices;

	@Autowired
	OauthClientService oauthClientService;

	@Bean
	public PasswordEncoder noOpPasswordEncoder() {
		return NoOpPasswordEncoder.getInstance();
	}

	/**
	 * 可以在这里将客户端数据替换成数据库配置。
	 * 
	 * @return
	 */
	@Bean
	public ClientDetailsService clientDetailsService() {
		InMemoryClientDetailsService inMemoryClientDetailsService = new InMemoryClientDetailsService();
		Map<String, ClientDetails> clientDetailsStore = new HashMap<>();

		List<OauthClientInfo> clientList = oauthClientService.findAll();
		for (OauthClientInfo item : clientList) {
			BaseClientDetails baseClientDetails = new BaseClientDetails();
			baseClientDetails.setClientId(item.getClientId());
			baseClientDetails.setClientSecret(item.getClientSecret());
			baseClientDetails.setAccessTokenValiditySeconds(item.getAccessTokenTime());
			baseClientDetails.setRefreshTokenValiditySeconds(item.getRefreshTokenTime());

			Set<String> salesWords = new HashSet<String>(Arrays.asList(item.getRedirectUrls().split(",")));
			baseClientDetails.setRegisteredRedirectUri(salesWords);
			List<String> scope = Arrays.asList(item.getScopes().split(","));
			baseClientDetails.setScope(scope);

			List<String> authorizedGrantTypes = Arrays.asList(item.getAuthorizedGrantTypes().split(","));
			baseClientDetails.setAuthorizedGrantTypes(authorizedGrantTypes);
			clientDetailsStore.put(item.getClientId(), baseClientDetails);
		}

		inMemoryClientDetailsService.setClientDetailsStore(clientDetailsStore);

		return inMemoryClientDetailsService;
	}

	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
		clients.withClientDetails(clientDetailsService());
	}

	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
		endpoints.authorizationCodeServices(authorizationCodeServices);
	}

	@Override
	public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
		oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("permitAll()").allowFormAuthenticationForClients();
	}
}
2.6 授权码长度控制AuthorizationCodeServices

InMemoryAuthorizationCodeServices :

代码语言:javascript
复制
package cn.pomit.springbootwork.oauthauth.token;

import java.util.concurrent.ConcurrentHashMap;

import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.code.RandomValueAuthorizationCodeServices;
import org.springframework.stereotype.Service;

@Service("authorizationCodeServices")
public class InMemoryAuthorizationCodeServices extends RandomValueAuthorizationCodeServices {
	protected final ConcurrentHashMap<String, OAuth2Authentication> authorizationCodeStore = new ConcurrentHashMap<String, OAuth2Authentication>();
	private RandomValueStringGenerator generator = new RandomValueStringGenerator(16);

	@Override
	protected void store(String code, OAuth2Authentication authentication) {
		this.authorizationCodeStore.put(code, authentication);
	}

	@Override
	public OAuth2Authentication remove(String code) {
		OAuth2Authentication auth = this.authorizationCodeStore.remove(code);
		return auth;
	}

	@Override
	public String createAuthorizationCode(OAuth2Authentication authentication) {
		String code = generator.generate();
		store(code, authentication);
		return code;
	}
}

它和Spring的InMemoryAuthorizationCodeServices没多大区别,区别在于RandomValueStringGenerator对象构造函数参数是16。

2.7 用户密码校验

SimpleAuthenticationProvider用来控制用户的校验,属于SpringSecurity的常用写法。

这里还可以根据用户类型对用户进行分类。如果Admin、User等

SimpleAuthenticationProvider:

代码语言:javascript
复制
package cn.pomit.springbootwork.oauthauth.provider;

import java.util.Collection;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.stereotype.Component;

import cn.pomit.springbootwork.oauthauth.domain.UserInfo;
import cn.pomit.springbootwork.oauthauth.service.UserInfoService;

@Component
public class SimpleAuthenticationProvider implements AuthenticationProvider {
	@Autowired
	private UserInfoService userInfoService;

	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		
		String userName = authentication.getPrincipal().toString();
		UserInfo user = userInfoService.getUserInfoByUserName(userName);

		if (user == null) {
			throw new BadCredentialsException("查无此用户");
		}
		if (user.getPasswd() != null && user.getPasswd().equals(authentication.getCredentials())) {
			Collection<? extends GrantedAuthority> authorities = null;
			if (user.getUserType() != null && user.getUserType().equals("2")) {
				authorities = AuthorityUtils
						.createAuthorityList("ROLE_ADMIN");
			} else {
				authorities = AuthorityUtils.createAuthorityList("ROLE_USER");
			}
			
			return new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPasswd(), authorities);
		} else {
			throw new BadCredentialsException("用户名或密码错误。");
		}
	}

	@Override
	public boolean supports(Class<?> arg0) {
		return true;
	}

}
2.8 Service、Mapper及一些简单的Handler

这里是我用到的一些Service、Mapper及简单的Handler,可以根据自己的需要随便改。

2.8.1 Service

OauthClientService:

代码语言:javascript
复制
package cn.pomit.springbootwork.oauthauth.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import cn.pomit.springbootwork.oauthauth.domain.OauthClientInfo;
import cn.pomit.springbootwork.oauthauth.mapper.TOauthClientMapper;

@Service
public class OauthClientService {
	
	@Autowired
	TOauthClientMapper tOauthClientMapper;
	
	public List<OauthClientInfo> findAll() {
		return tOauthClientMapper.findAll();
	}
}

UserInfoService:

代码语言:javascript
复制
package cn.pomit.springbootwork.oauthauth.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import cn.pomit.springbootwork.oauthauth.domain.UserInfo;
import cn.pomit.springbootwork.oauthauth.mapper.UserInfoMapper;

@Service
public class UserInfoService {
	@Autowired
	UserInfoMapper userInfoMapper;
	public UserInfo getUserInfoByUserName(String userName){
		return userInfoMapper.findByUserName(userName);
	}
	
	public List<UserInfo> page(int page, int size){
		return userInfoMapper.testPageSql(page, size);
	}
	
	public List<UserInfo> testTrimSql(UserInfo userInfo){
		return userInfoMapper.testTrimSql(userInfo);
	}
}
2.8.2 Mapper

TOauthClientMapper:

代码语言:javascript
复制
package cn.pomit.springbootwork.oauthauth.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import cn.pomit.springbootwork.oauthauth.domain.OauthClientInfo;

@Mapper
public interface TOauthClientMapper {
	@Select({
	   "<script>",
	        "SELECT ",
	        "client_id as clientId,client_secret as clientSecret,access_token_time as accessTokenTime,refresh_token_time as refreshTokenTime,redirect_urls as redirectUrls,scopes as scopes,authorized_grant_types as authorizedGrantTypes",
	        "FROM t_oauth_client",
	   "</script>"})
	List<OauthClientInfo> findAll();
	
}

UserInfoMapper:

代码语言:javascript
复制
package cn.pomit.springbootwork.oauthauth.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import cn.pomit.springbootwork.oauthauth.domain.UserInfo;

@Mapper
public interface UserInfoMapper {
	@Select({
		"<script>",
	        "SELECT ",
	        "user_name as userName,passwd,name,mobile,valid, user_type as userType",
	        "FROM user_info",
	        "WHERE user_name = #{userName,jdbcType=VARCHAR}",
	   "</script>"})
	UserInfo findByUserName(@Param("userName") String userName);
	
	@Select({
		"<script>",
	        "SELECT ",
	        "user_name as userName,passwd,name,mobile,valid, user_type as userType",
	        "FROM user_info",
	        "WHERE mobile = #{mobile,jdbcType=VARCHAR}",
            "<if test='userType != null and userType != \"\" '> and user_type = #{userType, jdbcType=VARCHAR} </if>",
	   "</script>"})
	List<UserInfo> testIfSql(@Param("mobile") String mobile,@Param("userType") String userType);
	
	@Select({
		"<script>",
			"SELECT ",
	        "user_name as userName,passwd,name,mobile,valid, user_type as userType",
	         "     FROM user_info ",
	         "    WHERE mobile IN (",
	         "   <foreach collection = 'mobileList' item='mobileItem' index='index' separator=',' >",
	         "      #{mobileItem}",
	         "   </foreach>",
	         "   )",
	    "</script>"})
	List<UserInfo> testForeachSql(@Param("mobileList") List<String> mobile);
	
	@Update({
		"<script>",
			"	UPDATE user_info",
	        "   SET ",
			"	<choose>",
			"	<when test='userType!=null'> user_type=#{user_type, jdbcType=VARCHAR} </when>",
			"	<otherwise> user_type='0000' </otherwise>",
			"	</choose>",
	        "  	WHERE user_name = #{userName, jdbcType=VARCHAR}",
	  	"</script>" })
	int testUpdateWhenSql(@Param("userName") String userName,@Param("userType") String userType);
	
	@Select({
		"<script>",
	 		"<bind name=\"tableName\" value=\"item.getIdentifyTable()\" />",
	 		"SELECT ",
	        "user_name as userName,passwd,name,mobile,valid, user_type as userType",
	        "FROM ${tableName}",
	        "WHERE mobile = #{item.mobile,jdbcType=VARCHAR}",
	   "</script>"})
	public List<UserInfo> testBindSql(@Param("item") UserInfo userInfo);
	
	@Select({
		"<script>",
	 		"<bind name=\"startNum\" value=\"page*pageSize\" />",
	 		"SELECT ",
	        "user_name as userName,passwd,name,mobile,valid, user_type as userType",
	        "FROM user_info",
	        "ORDER BY mobile ASC",
	        "LIMIT #{pageSize} OFFSET #{startNum}",
	   "</script>"})
	public List<UserInfo> testPageSql(@Param("page") int page, @Param("pageSize") int size);
	
	@Select({
		"<script>",
	 		"SELECT ",
	        "user_name as userName,passwd,name,mobile,valid, user_type as userType",
	        "FROM user_info",
	        "<trim prefix=\" where \" prefixOverrides=\"AND\">",
            	"<if test='item.userType != null and item.userType != \"\" '> and user_type = #{item.userType, jdbcType=VARCHAR} </if>",
            	"<if test='item.mobile != null and item.mobile != \"\" '> and mobile = #{item.mobile, jdbcType=VARCHAR} </if>",
	        "</trim>",
	   "</script>"})
	public List<UserInfo> testTrimSql(@Param("item") UserInfo userInfo);
	
	@Update({
		"<script>",
			"	UPDATE user_info",
	        "   <set> ",
	        "<if test='item.userType != null and item.userType != \"\" '>user_type = #{item.userType, jdbcType=VARCHAR}, </if>",
        	"<if test='item.mobile != null and item.mobile != \"\" '> mobile = #{item.mobile, jdbcType=VARCHAR} </if>",
			" 	</set>",
	        "  	WHERE user_name = #{item.userName, jdbcType=VARCHAR}",
	  	"</script>" })
	public int testSetSql(@Param("item") UserInfo userInfo);
}
2.8.3 Handler

AjaxAuthFailHandler:

代码语言:javascript
复制
package cn.pomit.springbootwork.oauthauth.handler;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;

import com.fasterxml.jackson.databind.ObjectMapper;

import cn.pomit.springbootwork.oauthauth.model.ResultCode;
import cn.pomit.springbootwork.oauthauth.model.ResultModel;

public class AjaxAuthFailHandler extends SimpleUrlAuthenticationFailureHandler {
	@Override
	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException exception) throws IOException, ServletException {
		if (isAjaxRequest(request)) {
			ResultModel rm = new ResultModel(ResultCode.CODE_00014.getCode(), exception.getMessage());
			ObjectMapper mapper = new ObjectMapper();
			response.setStatus(HttpStatus.OK.value());
			response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
			mapper.writeValue(response.getWriter(), rm);
		} else {
			setDefaultFailureUrl("/login.html");
			super.onAuthenticationFailure(request, response, exception);
		}
	}

	public static boolean isAjaxRequest(HttpServletRequest request) {
		String ajaxFlag = request.getHeader("X-Requested-With");
		return ajaxFlag != null && "XMLHttpRequest".equals(ajaxFlag);
	}
}

AjaxLogoutSuccessHandler:

代码语言:javascript
复制
package cn.pomit.springbootwork.oauthauth.handler;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;

import com.fasterxml.jackson.databind.ObjectMapper;

import cn.pomit.springbootwork.oauthauth.model.ResultCode;
import cn.pomit.springbootwork.oauthauth.model.ResultModel;

public class AjaxLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
	public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
			throws IOException, ServletException {
		if (isAjaxRequest(request)) {
			ResultModel rm = new ResultModel(ResultCode.CODE_00000);
			ObjectMapper mapper = new ObjectMapper();
			response.setStatus(HttpStatus.OK.value());
			response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
			mapper.writeValue(response.getWriter(), rm);
		} else {
			super.onLogoutSuccess(request, response, authentication);
		}
	}

	public static boolean isAjaxRequest(HttpServletRequest request) {
		String ajaxFlag = request.getHeader("X-Requested-With");
		return ajaxFlag != null && "XMLHttpRequest".equals(ajaxFlag);
	}
}

AuthSuccessHandler:

代码语言:javascript
复制
package cn.pomit.springbootwork.oauthauth.handler;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;

public class AuthSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
	protected final Log logger = LogFactory.getLog(this.getClass());

	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException {
		 RequestCache cache = new HttpSessionRequestCache();
         SavedRequest savedRequest = cache.getRequest(request, response);
         String url = savedRequest.getRedirectUrl();
         
         response.sendRedirect(url);
	}
}

UnauthorizedEntryPoint:

代码语言:javascript
复制
package cn.pomit.springbootwork.oauthauth.handler;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import com.fasterxml.jackson.databind.ObjectMapper;

import cn.pomit.springbootwork.oauthauth.model.ResultCode;
import cn.pomit.springbootwork.oauthauth.model.ResultModel;

public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
	@Override
	public void commence(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException authException) throws IOException, ServletException {
		if (isAjaxRequest(request)) {
			ResultModel rm = new ResultModel(ResultCode.CODE_40004);
			ObjectMapper mapper = new ObjectMapper();
			response.setStatus(HttpStatus.OK.value());
			response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
			mapper.writeValue(response.getWriter(), rm);
		} else {
			response.sendRedirect("/login.html");
		}

	}

	public static boolean isAjaxRequest(HttpServletRequest request) {
		String ajaxFlag = request.getHeader("X-Requested-With");
		return ajaxFlag != null && "XMLHttpRequest".equals(ajaxFlag);
	}
}
2.8.4 实体

UserInfo:

代码语言:javascript
复制
package cn.pomit.springbootwork.oauthauth.domain;

public class UserInfo {
	private String userName;
	private String passwd;
	private String name;
	private String mobile;
	private Integer valid;
	private String userType;

	public UserInfo() {

	}

	public UserInfo(UserInfo src) {
		this.userName = src.userName;
		this.passwd = src.passwd;
		this.name = src.name;
		this.mobile = src.mobile;
		this.valid = src.valid;
	}

	public UserInfo(String userName, String passwd, String name, String mobile, Integer valid) {
		super();
		this.userName = userName;
		this.passwd = passwd;
		this.name = name;
		this.mobile = mobile;
		this.valid = valid;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public String getUserName() {
		return userName;
	}

	public void setPasswd(String passwd) {
		this.passwd = passwd;
	}

	public String getPasswd() {
		return passwd;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setMobile(String mobile) {
		this.mobile = mobile;
	}

	public String getMobile() {
		return mobile;
	}

	public void setValid(Integer valid) {
		this.valid = valid;
	}

	public Integer getValid() {
		return valid;
	}

	public String getUserType() {
		return userType;
	}

	public void setUserType(String userType) {
		this.userType = userType;
	}

	public String getIdentifyTable(){
		return "user_info";
	}
}

OauthClientInfo:

代码语言:javascript
复制
package cn.pomit.springbootwork.oauthauth.domain;

public class OauthClientInfo {
	private String clientId;
	private String clientSecret;
	private int accessTokenTime;
	private int refreshTokenTime;
	private String redirectUrls;
	private String scopes;
	
	private String authorizedGrantTypes;

	public void setClientId(String clientId) {
		this.clientId = clientId;
	}

	public String getClientId() {
		return clientId;
	}

	public void setClientSecret(String clientSecret) {
		this.clientSecret = clientSecret;
	}

	public String getClientSecret() {
		return clientSecret;
	}

	public void setAccessTokenTime(int accessTokenTime) {
		this.accessTokenTime = accessTokenTime;
	}

	public int getAccessTokenTime() {
		return accessTokenTime;
	}

	public void setRefreshTokenTime(int refreshTokenTime) {
		this.refreshTokenTime = refreshTokenTime;
	}

	public int getRefreshTokenTime() {
		return refreshTokenTime;
	}

	public void setRedirectUrls(String redirectUrls) {
		this.redirectUrls = redirectUrls;
	}

	public String getRedirectUrls() {
		return redirectUrls;
	}

	public void setScopes(String scopes) {
		this.scopes = scopes;
	}

	public String getScopes() {
		return scopes;
	}

	public String getAuthorizedGrantTypes() {
		return authorizedGrantTypes;
	}

	public void setAuthorizedGrantTypes(String authorizedGrantTypes) {
		this.authorizedGrantTypes = authorizedGrantTypes;
	}

}
2.9 测试web

OauthAuthRest:

代码语言:javascript
复制
package cn.pomit.springbootwork.oauthauth.web;

import java.security.Principal;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import cn.pomit.springbootwork.oauthauth.model.IpModel;
import cn.pomit.springbootwork.oauthauth.model.ResultModel;
import cn.pomit.springbootwork.oauthauth.util.IPUtil;

@RestController
@RequestMapping("/api")
public class OauthAuthRest {

	@RequestMapping(value = "/ip", method = { RequestMethod.GET })
	public ResultModel ip(HttpServletRequest request) {
		IpModel ipModel = new IpModel();
		ipModel.setClientIpAddress(IPUtil.getIpAddr(request));
		ipModel.setServerIpAddress(IPUtil.localIp());
		return ResultModel.ok(ipModel);
	}

	@RequestMapping(value = "/userInfo")
	public ResultModel userinfo(Principal principal) {

		return ResultModel.ok(principal.getName());
	}

}

OauthTestRest:

代码语言:javascript
复制
package cn.pomit.springbootwork.oauthauth.web;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import cn.pomit.springbootwork.oauthauth.model.ResultModel;

@Controller
@RequestMapping("/")
public class OauthTestRest {

	@ResponseBody
	@RequestMapping(value = "/test/test", method = { RequestMethod.GET })
	public ResultModel test() {
		
		return ResultModel.ok("我就是test");
	}
	
	@RequestMapping(value = "/", method = { RequestMethod.GET })
	public String index() {
		
		return "/index.html";
	}
	
	
	@RequestMapping(value = "/oauth/confirm_access", method = { RequestMethod.GET })
	public String oauthAuthorize() {
		
		return "/authorize.html";
	}
}

这里的,/oauth/confirm_access路径,重定向到/authorize.html路径了。

2.10 授权相关页面

可以修改登录页面和授权页面,让自己的OAUTH2看起来更专业一点。

其中,授权页面的修改,需要自己在后端配置跳转,在2.9中有配置。

2.10.1 登录页面
代码语言:javascript
复制
<!DOCTYPE html>
<html>

<head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <title>品茗IT-*登录</title>

    <!-- CSS  -->
    <link href="https://lib.baomitu.com/material-design-icons/3.0.1/iconfont/material-icons.min.css" rel="stylesheet">
    <link href="https://lib.baomitu.com/materialize/0.100.2/css/materialize.min.css" type="text/css" rel="stylesheet" media="screen,projection"/>
  <style>

		.button-collapse {
            color: #26a69a;
        }
		a:link,a:visited{
			text-decoration:none;  
			color:black;
			font-family: "Roboto", sans-serif;
		}
		a:hover{
			text-decoration:none; 
			color:black;
			font-family: "Roboto", sans-serif;
		}
		#reg_a:link{
			color:#69F0AE;
		}
		#reg_a:visited{
			color:#00e676;
		}
		#reg_a:hover{
			color:#1669a7;
		}
		#forgot_a:link{
			color:#ff9800;
		}
		#forgot_a:visited{
			color:#ff9800;
		}
		#forgot_a:hover{
			color:#ffb74d;
		}
		
  </style>
</head>

<body>
	<!-- header start -->

<!-- header end -->

  <div class="section no-pad-bot" style="padding-top: 3rem;">
    <div class="container">
      <div class="row">
        <div class="col s12 m9 l6" style="padding: 3rem!important;">
          <h3>您好!</h3>
          <p>已经有账号,可以直接登录。</p>
          <form method ="post" action="/login">
            <div class="form-group"> <input type="text" class="form-control" id="mobileInput" name="userName" placeholder="请输入用户名或Email"> 
            </div>
            <div class="form-group"> <input type="password" class="form-control" placeholder="请输入密码" name="userPwd" id="passwdInput"> 
            </div> 
            
            <div>
	            <button type="submit" id="loginBtn" class="btn btn-primary">提交</button>
            </div>
          </form>
		  <br>
		  <div id="alertDiv" class="alert alert-danger" style="display:none">
			<a href="javascript:$('#alertDiv').hide();" class="close" >&times;</a>
			<strong>登录失败!</strong>
		  </div>
        </div>
      </div>
    </div>
  </div>
<script src="https://lib.baomitu.com/jquery/3.3.0/jquery.min.js"></script>
<script src="https://lib.baomitu.com/materialize/0.100.2/js/materialize.min.js"></script>

<script>
	$(function () {
        $('.button-collapse').sideNav();
        var origin = document.referrer;
        var url = window.location.href;
        var type = 0;
        var origin = document.referrer;
        if(url != null && url != ""){
            var argsIndex = url.split("?type=");
            if(argsIndex != null && argsIndex.length > 0){
            	type = argsIndex[1];
            }else{
            	var argsOrigin = url.split("?origin=");
            	if(argsOrigin != null && argsOrigin.length > 0){
            		origin = argsOrigin[1];
                }
            }
        }
		
    });
</script>
</body>
<script>
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "https://hm.baidu.com/hm.js?e553ae2bb23494dee9b6f43415a1ce5a";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script>

</html>
2.9.2 授权页面
代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <meta name="keywords" content="授权登录">
    <meta name="description" content="授权登录">
    <title>授权登录</title>

    <!-- CSS  -->
    <link href="https://lib.baomitu.com/material-design-icons/3.0.1/iconfont/material-icons.min.css" rel="stylesheet">
    <link href="https://lib.baomitu.com/materialize/0.100.2/css/materialize.min.css" type="text/css" rel="stylesheet" media="screen,projection"/>
     <style>
	/* Custom Stylesheet */
        /**
         * Use this file to override Materialize files so you can update
         * the core Materialize files in the future
         *
         * Made By MaterializeCSS.com
         */

        body{
        	background: #fff6f6
        }

        p {
            line-height: 2rem;
        }

        .button-collapse {
            color: #26a69a;
        }

        .parallax-container {
            min-height: 380px;
            line-height: 0;
            height: auto;
            color: rgba(255,255,255,.9);
        }
        .parallax-container .section {
            width: 100%;
        }

        @media only screen and (max-width : 992px) {
            .parallax-container .section {
                position: absolute;
                top: 40%;
            }
            #index-banner .section {
                top: 10%;
            }
        }

        @media only screen and (max-width : 600px) {
            #index-banner .section {
                top: 0;
            }
        }

        .icon-block {
            padding: 0 15px;
        }
        .icon-block .material-icons {
            font-size: inherit;
        }

        footer.page-footer {
            margin: 0;
        }
        
        .token.punctuation {
		    color: #999;
		}
		.bar-active{
			background: #f5c2c2b3;
		}
		.bar-doc{
			background-color: #f7a1a1;
		}
		.center-block{
			display: block;
			margin-left: auto;
			margin-right: auto;
			text-align: -webkit-center;
		}
		.searchInput{
		    display: block;
		    width: 100%;
		    height: 34px;
		    padding: 6px 12px;
		    font-size: 14px;
		    line-height: 1.42857143;
		    color: #555;
		    background-color: #fff;
		    background-image: none;
		    border: 1px solid #ccc;
		    border-radius: 4px;
		    -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
		    box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
		    -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
		    -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
		    transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
		}
		td{
			padding: 10px 5px !important;
		}
    </style>
</head>
<body>

<!-- content -->
<div class="section no-pad-bot">
    <div class="container">
        <div class="row center">
	        <span class="teal-text text-lighten-2 center" style="font-size: 1.5em;font-weight:bold;color: #112f6b!important;">授权登录</span>
        </div>
    </div>
</div>
<!-- content -->
<div class="section no-pad-bot">
    <div class="container">
    	<div class="row center">
    		<img src="/img/bg.jpg"/>
		</div>
		<form id="confirmationForm" name="confirmationForm" action="/oauth/authorize" method="post">
			<input name="user_oauth_approval" value="true" type="hidden"/>
			<input name="authorize" value="Authorize" type="hidden"/>
			<div class="row center">
	    		<button type="submit" name="scope.trust" value="true" class="waves-effect waves-light btn">授权</button>
			</div> 
			<div class="row center">
	    		<button type="submit" name="scope.trust" value="false" class="waves-effect waves-light btn grey">取消</button>
			</div> 
		</form>
    </div>
</div>

</body>

<script src="https://lib.baomitu.com/jquery/3.3.0/jquery.min.js"></script>
<script src="https://www.pomit.cn/js/FileSaver.min.js"></script>
<script src="https://lib.baomitu.com/materialize/0.100.2/js/materialize.min.js"></script>
<script>
	
    $(function () {
    	
    });
</script>
</html>
2.11 其他实体

IPUtil:

代码语言:javascript
复制
package cn.pomit.springbootwork.oauthauth.util;

import java.net.InetAddress;
import java.net.UnknownHostException;

import javax.servlet.http.HttpServletRequest;

public class IPUtil {
	/**
	 * @Description: 获取客户端IP地址
	 */
	public static String getIpAddr(HttpServletRequest request) {
		String ip = request.getHeader("x-forwarded-for");
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("WL-Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getRemoteAddr();
			if (ip.equals("127.0.0.1")) {
				// 根据网卡取本机配置的IP
				InetAddress inet = null;
				try {
					inet = InetAddress.getLocalHost();
				} catch (Exception e) {
					e.printStackTrace();
				}
				ip = inet.getHostAddress();
			}
		}
		// 多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
		if (ip != null && ip.length() > 15) {
			if (ip.indexOf(",") > 0) {
				ip = ip.substring(0, ip.indexOf(","));
			}
		}
		return ip;
	}

	/**
	 * 获取的是本地的IP地址
	 * 
	 * @return
	 */
	public static String localIp() {
		String result = "";
		try {
			InetAddress address = InetAddress.getLocalHost();
			result = address.getHostAddress();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
		return result;
	}
}

ResultModel:

代码语言:javascript
复制
package cn.pomit.springbootwork.oauthauth.model;

/**
 * @author cff
 */
public class ResultModel {
	private String errorCode;
    private String message;
    private Object data;

    public ResultModel() {

    }

    public ResultModel(String errorCode, String message) {
        this.errorCode = errorCode;
        this.message = message;
    }

    public ResultModel(String errorCode, String message, Object data) {
        this.errorCode = errorCode;
        this.message = message;
        this.data = data;
    }

    public String getErrorCode() {
        return errorCode;
    }

    public void set	ErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public static ResultModel ok(String testValue) {
        ResultModel rm = new ResultModel();
        rm.setData(testValue);
        return rm;
    }
}

品茗IT-博客专题:https://www.pomit.cn/lecture.html汇总了Spring专题Springboot专题SpringCloud专题web基础配置专题。

快速构建项目

Spring项目快速开发工具:

一键快速构建Spring项目工具

一键快速构建SpringBoot项目工具

一键快速构建SpringCloud项目工具

一站式Springboot项目生成

Mysql一键生成Mybatis注解Mapper

Spring组件化构建

SpringBoot组件化构建

SpringCloud服务化构建

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-05-11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SpringCloud微服务实战系列(十八)Ouath2在真实场景中的应用之授权服务器
  • 一、概述
  • 二、授权服务器
    • 2.1 引入依赖
      • 2.2 配置文件
        • 2.3 启动
          • 2.4 安全控制配置
            • 2.5 授权服务器配置
              • 2.6 授权码长度控制AuthorizationCodeServices
                • 2.7 用户密码校验
                  • 2.8 Service、Mapper及一些简单的Handler
                    • 2.8.1 Service
                    • 2.8.2 Mapper
                    • 2.8.3 Handler
                    • 2.8.4 实体
                  • 2.9 测试web
                    • 2.10 授权相关页面
                      • 2.10.1 登录页面
                      • 2.9.2 授权页面
                    • 2.11 其他实体
                    • 快速构建项目
                    相关产品与服务
                    访问管理
                    访问管理(Cloud Access Management,CAM)可以帮助您安全、便捷地管理对腾讯云服务和资源的访问。您可以使用CAM创建子用户、用户组和角色,并通过策略控制其访问范围。CAM支持用户和角色SSO能力,您可以根据具体管理场景针对性设置企业内用户和腾讯云的互通能力。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档