基于JWT实现SSO单点登录

实现原理

用户访问应用A 时,需要到认证服务上进行认证。认证成功后,再去访问应用B,仍然需要到认证服务器认证,只不过这次不用再重新登录了,只需要授一下权就可以了。
基于JWT实现SSO单点登录_第1张图片

代码结构

  • sso-server :认证服务器
  • sso-client1:应用A
  • sso-client2:应用B
  com.imooc.sso
    sso-demo
    1.0-SNAPSHOT
    
        sso-server
        sso-client1
        sso-client2
    
    pom
    单点登录demo

认证服务器开发

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.写一个简单的前端





SSO Client1


	

SSO Demo Client1

访问Client2

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

我们先来访问应用A

在这里插入代码片

会直接引导我们跳转到认证服务器上,输入用户名和密码后,跳转到授权界面
基于JWT实现SSO单点登录_第2张图片
点击授权,完成登录
基于JWT实现SSO单点登录_第3张图片
此时我们点击 访问Client2,同样会跳转到认证服务器,但是这次不用登录,直接授权就可以了
基于JWT实现SSO单点登录_第4张图片

改善代码

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

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 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 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 model, HttpServletRequest request) {
		StringBuilder builder = new StringBuilder("
    "); @SuppressWarnings("unchecked") Map scopes = (Map) (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("
"); return builder.toString(); } private static String CSRF = ""; private static String DENIAL = "
%csrf%
"; private static String TEMPLATE = "

OAuth Approval

" + "

Do you authorize '${authorizationRequest.clientId}' to access your protected resources?

" + "
%csrf%%scopes%
" + "%denial%
"; private static String SCOPE = "
  • %scope%: Approve Deny
  • "; }

    源码地址

    你可能感兴趣的:(Spring,Security)