基于JWT实现SSO单点登录

实现原理

用户访问应用A 时,需要到认证服务上进行认证。认证成功后,再去访问应用B,仍然需要到认证服务器认证,只不过这次不用再重新登录了,只需要授一下权就可以了。
在这里插入图片描述

代码结构

  • sso-server :认证服务器
  • sso-client1:应用A
  • sso-client2:应用B
  <groupId>com.imooc.sso</groupId>
    <artifactId>sso-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>sso-server</module>
        <module>sso-client1</module>
        <module>sso-client2</module>
    </modules>
    <packaging>pom</packaging>
    <description>单点登录demo</description>

认证服务器开发

1.配置客户端的 clientId 和 clientSecert, 设置JwtToekenStore

@Configuration
@EnableAuthorizationServer
public class SsoAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
	
	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
		clients.inMemory()
				.withClient("imooc1")
				.secret("imoocsecrect1")
				.authorizedGrantTypes("authorization_code", "refresh_token")
				.scopes("all")
				.and()
				.withClient("imooc2")
				.secret("imoocsecrect2")
				.authorizedGrantTypes("authorization_code", "refresh_token")
				.scopes("all");
	}
	
	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
		endpoints.tokenStore(jwtTokenStore()).accessTokenConverter(jwtAccessTokenConverter());
	}
	
	@Override
	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
		security.tokenKeyAccess("isAuthenticated()");//访问需认证,默认denyAll
	}
	
	@Bean
	public TokenStore jwtTokenStore() {
		return new JwtTokenStore(jwtAccessTokenConverter());
	}
	
	@Bean
	public JwtAccessTokenConverter jwtAccessTokenConverter(){
		JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey("imooc");
        return converter;
	}

}

2.配置用户密码

server.port = 9999
server.context-path = /server
security.user.password = 123456

应用A

1.@EnableOAuth2Sso

@SpringBootApplication
@RestController
@EnableOAuth2Sso
public class SsoClient1Application {
	
	@GetMapping("/user")
	public Authentication user(Authentication user) {
		return user;
	}

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

2.添加配置文件

security.oauth2.client.clientId = imooc1
security.oauth2.client.clientSecret = imoocsecrect1
security.oauth2.client.user-authorization-uri = http://127.0.0.1:9999/server/oauth/authorize
security.oauth2.client.access-token-uri = http://127.0.0.1:9999/server/oauth/token
security.oauth2.resource.jwt.key-uri = http://127.0.0.1:9999/server/oauth/token_key

server.port = 8080
server.context-path = /client1

3.写一个简单的前端

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>SSO Client1</title>
</head>
<body>
	<h1>SSO Demo Client1</h1>
	<a href="http://127.0.0.1:8060/client2/index.html">访问Client2</a>
</body>
</html>

应用B和应用A类似,这样一个简单的SSO Demo就搭建好了。启动来看一下

我们先来访问应用A

在这里插入代码片

会直接引导我们跳转到认证服务器上,输入用户名和密码后,跳转到授权界面
在这里插入图片描述
点击授权,完成登录
在这里插入图片描述
此时我们点击 访问Client2,同样会跳转到认证服务器,但是这次不用登录,直接授权就可以了
在这里插入图片描述

改善代码

  • 登录方法改为表达登录
  • 省去用户去授权,默认直接授权

1.设置表单登录

@Configuration
public class SsoSecurityConfig extends WebSecurityConfigurerAdapter {
	
	@Autowired
	private UserDetailsService userDetailsService;
	
	@Bean
	public PasswordEncoder passwordEncoder()	{
		return new BCryptPasswordEncoder();
	}
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
	}
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.formLogin().and().authorizeRequests().anyRequest().authenticated();
	}

}

2.重写授权的逻辑

隐藏授权界面,并直接提交

@RestController
@SessionAttributes("authorizationRequest")
public class SsoApprovalEndpoint {
	
	@RequestMapping("/oauth/confirm_access")
	public ModelAndView getAccessConfirmation(Map<String, Object> model, HttpServletRequest request) throws Exception {
		String template = createTemplate(model, request);
		if (request.getAttribute("_csrf") != null) {
			model.put("_csrf", request.getAttribute("_csrf"));
		}
		return new ModelAndView(new SsoSpelView(template), model);
	}

	protected String createTemplate(Map<String, Object> model, HttpServletRequest request) {
		String template = TEMPLATE;
		if (model.containsKey("scopes") || request.getAttribute("scopes") != null) {
			template = template.replace("%scopes%", createScopes(model, request)).replace("%denial%", "");
		}
		else {
			template = template.replace("%scopes%", "").replace("%denial%", DENIAL);
		}
		if (model.containsKey("_csrf") || request.getAttribute("_csrf") != null) {
			template = template.replace("%csrf%", CSRF);
		}
		else {
			template = template.replace("%csrf%", "");
		}
		return template;
	}

	private CharSequence createScopes(Map<String, Object> model, HttpServletRequest request) {
		StringBuilder builder = new StringBuilder("<ul>");
		@SuppressWarnings("unchecked")
		Map<String, String> scopes = (Map<String, String>) (model.containsKey("scopes") ? model.get("scopes") : request
				.getAttribute("scopes"));
		for (String scope : scopes.keySet()) {
			String approved = "true".equals(scopes.get(scope)) ? " checked" : "";
			String denied = !"true".equals(scopes.get(scope)) ? " checked" : "";
			String value = SCOPE.replace("%scope%", scope).replace("%key%", scope).replace("%approved%", approved)
					.replace("%denied%", denied);
			builder.append(value);
		}
		builder.append("</ul>");
		return builder.toString();
	}

	private static String CSRF = "<input type='hidden' name='${_csrf.parameterName}' value='${_csrf.token}' />";

	private static String DENIAL = "<form id='denialForm' name='denialForm' action='${path}/oauth/authorize' method='post'><input name='user_oauth_approval' value='false' type='hidden'/>%csrf%<label><input name='deny' value='Deny' type='submit'/></label></form>";

	private static String TEMPLATE = "<html><body><div style='display:none;'><h1>OAuth Approval</h1>"
			+ "<p>Do you authorize '${authorizationRequest.clientId}' to access your protected resources?</p>"
			+ "<form id='confirmationForm' name='confirmationForm' action='${path}/oauth/authorize' method='post'><input name='user_oauth_approval' value='true' type='hidden'/>%csrf%%scopes%<label><input name='authorize' value='Authorize' type='submit'/></label></form>"
			+ "%denial%</div><script>document.getElementById('confirmationForm').submit()</script></body></html>";

	private static String SCOPE = "<li><div class='form-group'>%scope%: <input type='radio' name='%key%'"
			+ " value='true'%approved%>Approve</input> <input type='radio' name='%key%' value='false'%denied%>Deny</input></div></li>";


}

源码地址

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值