OAuth2与spring-security-oauth2

OAuth2

OAuth2是什么

关于这块概念可以参考: https://www.jianshu.com/p/84a4b4a1e833

OAuth2中4种授权模式作用场景

  1. 授权码模式
    授权码模式一般用于提供给第三方使用
  2. 简化模式
    简化模式一般不会使用
  3. 密码模式
    密码模式一般仅用于系统内部使用
  4. 客户端凭证模式
    客户端凭证模式一般不会使用

spring-security-oauth2

spring-security-oauth2是什么

spring-security-oauth2是基于spring-security框架完整实现oauth2协议的框架,具有oauth2中4种模式访问和第三方登录等功能。

搭建spring-security-oauth2案例

环境版本

	<properties>
		<java.version>1.8</java.version>
		<spring.cloud.version>Hoxton.SR9</spring.cloud.version>
		<spring.boot.version>2.3.0.RELEASE</spring.boot.version>
		<mysql.connector.version>8.0.15</mysql.connector.version>
	</properties>

完整依赖

pom.xml

	<dependencies>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-security</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-oauth2</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-jwt</artifactId>
			<version>1.1.0.RELEASE</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>${mysql.connector.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>

	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-dependencies</artifactId>
				<version>${spring.boot.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>

			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring.cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

application.yml


server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://192.168.174.129:3306/oauth?serverTimezone=GMT%2B8&characterEncoding=utf-8&autoReconnect=true&allowMultiQueries=true
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver


目录结构

在这里插入图片描述DemoAuthorizationServerConfiguration:授权服务配置

@Configuration
@EnableAuthorizationServer
@AllArgsConstructor
public class DemoAuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

	private DataSource dataSource;

	private TokenStore tokenStore;

	private AuthenticationManager authenticationManager;

	private UserDetailsService userDetailsService;

	private TokenEnhancer tokenEnhancer;

	private JwtAccessTokenConverter jwtAccessTokenConverter;

	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
		// @formatter:off
		clients.inMemory()
				.withClient("test-client").secret("$2a$08$YGw560YLRWHg3Hl29ZlmdOfAeyRQ2u0kDiqUyQ62Y1pkW5n4a.hjO")
				.authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
				.authorities("ROLE_CLIENT").redirectUris("http://localhost:8084/oauth/callback")
				.scopes("read", "write", "all");// 请求参数scope必须为集合中的某个值
		// @formatter:on
	}

	@Override
	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
		// 允许表单传参client信息
		security.allowFormAuthenticationForClients()
				.tokenKeyAccess("permitAll()")
				.checkTokenAccess("isAuthenticated()");
	}

	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
		endpoints.tokenStore(tokenStore)
				.authenticationManager(authenticationManager)
				.userDetailsService(userDetailsService)
				.tokenEnhancer(tokenEnhancer)
				.accessTokenConverter(jwtAccessTokenConverter);
	}

}

DemoJwtTokenStoreConfiguration:JwtToken仓库配置

@Configuration
public class DemoJwtTokenStoreConfiguration {


	@Bean
	public TokenStore tokenStore(JwtAccessTokenConverter tokenConverter) {
		return new JwtTokenStore(tokenConverter);
	}

	@Bean
	public JwtAccessTokenConverter tokenConverter() {
		JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
		tokenConverter.setSigningKey("abc");
		return tokenConverter;
	}

	@Bean
	public TokenEnhancer tokenEnhancer(JwtAccessTokenConverter tokenConverter) {
		return new DemoJwtTokenEnhancer(tokenConverter);
	}

}

DemoResourceServerConfiguration:资源服务配置

@Configuration
@EnableResourceServer
@AllArgsConstructor
public class DemoResourceServerConfiguration extends ResourceServerConfigurerAdapter {

	private AccessDeniedHandler accessDeniedHandler;

//	private AuthenticationEntryPoint authenticationEntryPoint;

	@Override
	public void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
				.anyRequest().authenticated().and()
				.exceptionHandling()
				.accessDeniedHandler(accessDeniedHandler)
//				.authenticationEntryPoint(authenticationEntryPoint)
				.and().csrf().disable();
	}

	@Override
	public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
		super.configure(resources);
	}

}

DemoWebSecurityConfiguration:WebSecurity配置

@Configuration
public class DemoWebSecurityConfiguration extends WebSecurityConfigurerAdapter {

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

	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2A, 8);
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.httpBasic().and().csrf().disable();
	}
}

DemoController:受保护的Controller资源

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

	@RequestMapping("/test")
	public Map<String, Object> test() {
		Map<String, Object> result = new HashMap<>();
		result.put("time", System.currentTimeMillis());
		return result;
	}

}

DemoClientDetailsService:ClientDetailsService实现类

public class DemoClientDetailsService extends JdbcClientDetailsService {

	public DemoClientDetailsService(DataSource dataSource) {
		super(dataSource);
	}

}

DemoUserDetails:用户信息类

@Getter
public class DemoUserDetails extends User {

	/**
	 * 用户id
	 */
	private String userId;

	/**
	 * 手机号
	 */
	private String mobile;

	/**
	 * 是否超级管理员 1是 0否
	 */
	private Boolean administrator;

	/**
	 * 角色id
	 */
	private List<Long> roles;

	public DemoUserDetails(String userId, String mobile, String username, String password, boolean enabled, List<Long> roles, boolean accountNonExpired,
						   boolean credentialsNonExpired, boolean accountNonLocked,
						   Collection<? extends GrantedAuthority> authorities, Boolean administrator) {
		super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked,
				authorities);
		this.userId = userId;
		this.roles = roles;
		this.administrator = administrator;
		this.mobile = mobile;
	}

}

DemoUserDetailsServiceImpl:UserDetailsService实现类

@Service
public class DemoUserDetailsServiceImpl implements UserDetailsService {

	@Override
	public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
		return new DemoUserDetails("userId", "mobile", s, "$2a$08$EiGfaup8QkkjT6DhvCSFRuNhdFTEV7Rbu/avUm8lGL2ZUji/lTWji", true, Arrays.asList(1L), true, true, true,
				AuthorityUtils.commaSeparatedStringToAuthorityList("1,2"), false);
	}

}

DemoAccessDeniedHandler:403处理器

@Component
public class DemoAccessDeniedHandler implements AccessDeniedHandler {

	@Override
	public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
		httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
		httpServletResponse.setContentType("application/json;charset=UTF-8");
		PrintWriter out = httpServletResponse.getWriter();
		out.write(new ObjectMapper().writeValueAsString("权限不足,请联系管理员!"));
		out.flush();
		out.close();
	}

}

DemoAuthenticationEntryPoint:401处理器

//@Component
public class DemoAuthenticationEntryPoint implements AuthenticationEntryPoint {

	@Override
	public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
		httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
		httpServletResponse.setContentType("application/json;charset=UTF-8");
		PrintWriter out = httpServletResponse.getWriter();
		out.write(new ObjectMapper().writeValueAsString("请先登录!"));
		out.flush();
		out.close();
	}

}

DemoJwtTokenEnhancer:TokenEnhancer实现类

@AllArgsConstructor
public class DemoJwtTokenEnhancer implements TokenEnhancer {

	private JwtAccessTokenConverter jwtAccessTokenConverter;

	@Override
	public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
		DemoUserDetails principal =
				(DemoUserDetails) authentication.getUserAuthentication().getPrincipal();
		Map<String, Object> info = new HashMap<>();
		info.put("user_id", principal.getUserId() == null ? "" : String.valueOf(principal.getUserId()));
		((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
		return jwtAccessTokenConverter.enhance(accessToken, authentication);
	}

}

DemoPasswordEncoder:PasswordEncoder实现类

public class DemoPasswordEncoder implements PasswordEncoder {

	@Override
	public String encode(CharSequence rawPassword) {
		return (String) rawPassword;
	}

	@Override
	public boolean matches(CharSequence rawPassword, String encodedPassword) {
		return encodedPassword.equals(encode(rawPassword));
	}

}

密码模式

获取token

/oauth/token,获取token,access_token就是我们需要在请求头中携带的token。
在这里插入图片描述
访问url:
http://localhost:8080/oauth/token?username=admin&password=qwer1234&grant_type=password&scope=all
并在请求头中加入basic auth参数
在这里插入图片描述眼力好的同学已经看出来上图中已经在url中出现了client…
也可以直接访问这个url:
http://localhost:8080/oauth/token?username=admin&password=qwer1234&grant_type=password&scope=all&client_id=test-client&client_secret=abc
这种访问方式又是哪里出来的?第一个java代码块里面就有配置了…
allowFormAuthenticationForClients又是怎么实现的呢?

	@Override
	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
		// 允许表单传参client信息
		security.allowFormAuthenticationForClients()
				.tokenKeyAccess("permitAll()")
				.checkTokenAccess("isAuthenticated()");
	}

访问受保护的资源

访问受保护的资源
图片中就是在请求头中将入key为Authorization的值
Authorization Bearer eyJhbGciOiJ…
在这里插入图片描述

在这里插入图片描述

授权码模式

获取code

直接在浏览器上访问该链接
http://localhost:8080/oauth/authorize?response_type=code&scope=all&client_id=test-client&client_secret=abc&redirect_uri=http://localhost:8084/oauth/callback

浏览器弹出httpBasic验证
在这里插入图片描述输入预设用户名/密码:admin/qwer1234
点击确定,弹出OAuth2用户授权页面,点击授权Authorize
在这里插入图片描述页面重定向到redirect_uri参数指定的url并附带了code
在这里插入图片描述

获取token

使用上一步中获取的code替换下面的,并访问url:
http://localhost:8080/oauth/token?grant_type=authorization_code&client_id=test-client&client_secret=abc&redirect_uri=http://localhost:8084/oauth/callback&code=OWwAmR

不会有人直接复制链接不改code参数吧?不会吧,不会吧

访问受保护的资源

该步骤与密码模式中一致

结语

spring-security-oauth2秉承spring一贯风格特色,配置多种多样,如果见到和上面配置不一样很正常,如果想要深入了解,需要仔细阅读源码。上面的案例只是简单的入门描述,由此可以引发很多疑问。

  1. 使用数据库配置client与user怎么做
  2. 需要配置手机号验证码登录等其他方式需要怎么做
  3. 授权码模式需要用户手动授权,如果特殊情况下改为不需要授权怎么做
  4. 需要在网关中使用,该怎么做
  5. 需要权限验证,该怎么做
  6. 基于spring-security-oauth2框架的单点登录怎么做

关注点赞,下次继续更新
stuck_out_tongue

配置篇 spring-security-oauth2 配置(一)

源码分析篇A路线 spring-security-oauth2 源码分析A路线(一)

源码分析篇B路线 spring-security-oauth2 源码分析B路线(一)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值