spring security oauth 开发sso单点登录
1. 创建项目
sso-demo 父项目
sso-server 认证服务器
sso-client1
sso-client2
引入jar包
1.创建 sso-demo 项目 pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | <?xml version= "1.0" encoding= "UTF-8" ?> <project xmlns= "http://maven.apache.org/POM/4.0.0" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation= "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion> 4.0 . 0 </modelVersion> <groupId>com.imooc.sso</groupId> <artifactId>sso-demo</artifactId> <packaging>pom</packaging> <version> 1.0 . 0 -SNAPSHOT</version> <modules> <module>sso-server</module> <module>sso-client1</module> <module>sso-client2</module> </modules> <!--由spring platform 来管理版本依赖版本--> <dependencyManagement> <dependencies> <dependency> <groupId>io.spring.platform</groupId> <artifactId>platform-bom</artifactId> <version>Brussels-SR4</version> <type>pom</type> <scope> import </scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.SR2</version> <type>pom</type> <scope> import </scope> </dependency> </dependencies> </dependencyManagement> <!--编译java版本--> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version> 2.3 . 2 </version> <configuration> <source> 1.8 </source> <target> 1.8 </target> <encoding>UTF- 8 </encoding> </configuration> </plugin> </plugins> </build> </project> |
创建 sso-server 认证服务器 pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <?xml version= "1.0" encoding= "UTF-8" ?> <project xmlns= "http://maven.apache.org/POM/4.0.0" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation= "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <parent> <artifactId>sso-demo</artifactId> <groupId>com.imooc.sso</groupId> <version> 1.0 . 0 -SNAPSHOT</version> </parent> <modelVersion> 4.0 . 0 </modelVersion> <artifactId>sso-server</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth</groupId> <artifactId>spring-security-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> </dependency> </dependencies> </project> |
SsoAuthorizationServerConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | package com.imooc.sso.server; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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.token.TokenStore; import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; import org.springframework.security.oauth2.provider.token.store.JwtTokenStore; /** * @Title: SsoAuthorizationServerConfig * @ProjectName ssodemo * @date 2021-01-0214:17 */ @Configuration @EnableAuthorizationServer public class SsoAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient( "imooc1" ) // 授权的应用 .secret( "imoocsecret1" ) // 密码 .authorizedGrantTypes( "authorization_code" , "refresh_token" ) // 授权的类型 .scopes( "all" ) // 范围 .autoApprove( true ) .and() .withClient( "imooc2" ) .secret( "imoocsecret2" ) .authorizedGrantTypes( "authorization_code" , "refresh_token" ) .scopes( "all" ) .autoApprove( true ); } /** * 配置jwt token * @return */ @Bean public TokenStore jwtTokenStore() { return new JwtTokenStore(jwtAccessTokenConvert()); } @Bean public JwtAccessTokenConverter jwtAccessTokenConvert() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); // jwt token 签名key converter.setSigningKey( "imooc" ); return converter; } /** * 使jwt token 生效 */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(jwtTokenStore()).accessTokenConverter(jwtAccessTokenConvert()); } /** * 安全配置, 用户访问token Key 的时候 需要身份认证. */ @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.tokenKeyAccess( "isAuthenticated()" ); } } |
SsoUserDetailsService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | package com.imooc.sso.server; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.authority.AuthorityUtils; 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.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; /** * @Title: SsoUserDetailsService * @ProjectName ssodemo * @date 2021-01-0215:10 */ /** * 配置用户名密码,不使用配置文件 */ @Component public class SsoUserDetailsService implements UserDetailsService { @Autowired private PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return new User(username, passwordEncoder.encode( "123456" ), true , true , true , true , AuthorityUtils.commaSeparatedStringToAuthorityList( "admin,ROLE_USER" )); } } |
SsoSecurityConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | package com.imooc.sso.server; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; 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.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; /** * @Title: SsoSecurityConfig * @ProjectName ssodemo * @date 2021-01-0215:06 */ @Configuration public class SsoSecurityConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Autowired private SsoUserDetailsService ssoUserDetailsService; /** * 配置自己的userDetail 和 密码加密器 * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(ssoUserDetailsService).passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http .formLogin() // 表单登录 .and() .authorizeRequests().anyRequest().authenticated(); // 所有请求都需要身份认证 } } |
自动提交授权
SpelView.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | package com.imooc.sso.server; import org.springframework.context.expression.MapAccessor; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.util.PropertyPlaceholderHelper; import org.springframework.web.servlet.View; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.HashMap; import java.util.Map; /** * @Title: SpelView * @ProjectName ssodemo * @date 2021-01-0215:22 */ class SpelView implements View { private final String template; private final String prefix; private final SpelExpressionParser parser = new SpelExpressionParser(); private final StandardEvaluationContext context = new StandardEvaluationContext(); private PropertyPlaceholderHelper.PlaceholderResolver resolver; public SpelView(String template) { this .template = template; this .prefix = new RandomValueStringGenerator().generate() + "{" ; this .context.addPropertyAccessor( new MapAccessor()); this .resolver = new PropertyPlaceholderHelper.PlaceholderResolver() { public String resolvePlaceholder(String name) { Expression expression = parser.parseExpression(name); Object value = expression.getValue(context); return value == null ? null : value.toString(); } }; } public String getContentType() { return "text/html" ; } public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { Map<String, Object> map = new HashMap<String, Object>(model); String path = ServletUriComponentsBuilder.fromContextPath(request).build() .getPath(); map.put( "path" , (Object) path== null ? "" : path); context.setRootObject(map); String maskedTemplate = template.replace( "${" , prefix); PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper(prefix, "}" ); String result = helper.replacePlaceholders(maskedTemplate, resolver); result = result.replace(prefix, "${" ); response.setContentType(getContentType()); response.getWriter().append(result); } } |
SsoApprovalEndpoint.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | package com.imooc.sso.server; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import java.util.Map; /** * @Title: SsoApprovalEndpoint * @ProjectName ssodemo * @date 2021-01-0215:21 */ //@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 SpelView(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 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%</body><script>document.getElementById('confirmationForm').submit()</script></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>" ; } |
application.properties
1 2 3 | server.port= 9999 server.context-path= /server #security.user.password= 123456 |
创建 sso-client1 项目
SsoClient1Application.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | package com.imooc.sso.client; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @Title: SsoClient1Application * @ProjectName ssodemo * @date 2021-01-0214:37 */ @SpringBootApplication @RestController @EnableOAuth2Sso public class SsoClient1Application { public static void main(String[] args) { SpringApplication.run(SsoClient1Application. class , args); } @GetMapping ( "/user" ) public Authentication user(Authentication user) { return user; } } |
application.properties
1 2 3 4 5 6 7 8 9 10 11 | security.oauth2.client.clientId=imooc1 security.oauth2.client.clientSecret=imoocsecret1 # 需要授权时,跳转认证服务器,获取授权码 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 # 获取jwt秘钥 (获取秘钥也需要认证,所以会携带 clientId和clientSecret) security.oauth2.resource.jwt.key-uri=http: //127.0.0.1:9999/server/oauth/token_key server.port= 8080 server.context-path=/client1 |
resources/static/index.html
1 2 3 4 5 6 7 8 9 10 11 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title>Client1</title> </head> <body> <h1>SSO Demo client1 </h1> <a href= "http://127.0.0.1:8060/client2/index.html" >访问Client2</a> </body> </html> |
创建 sso-client2 项目
SsoClient2Application.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | package com.imooc.sso.client; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * @Title: SsoClient1Application * @ProjectName ssodemo * @date 2021-01-0214:37 */ @SpringBootApplication @RestController @EnableOAuth2Sso public class SsoClient2Application { public static void main(String[] args) { SpringApplication.run(SsoClient2Application. class , args); } @GetMapping ( "/user" ) public Authentication user(Authentication user) { return user; } } |
application.properties
1 2 3 4 5 6 7 8 9 10 11 | security.oauth2.client.clientId=imooc2 security.oauth2.client.clientSecret=imoocsecret2 # 需要授权时,跳转认证服务器,获取授权码 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 # 获取jwt秘钥 (获取秘钥也需要认证,所以会携带 clientId和clientSecret) security.oauth2.resource.jwt.key-uri=http: //127.0.0.1:9999/server/oauth/token_key server.port= 8060 server.context-path=/client2 |
resources/static/index.html
1 2 3 4 5 6 7 8 9 10 11 | <!DOCTYPE html> <html lang= "en" > <head> <meta charset= "UTF-8" > <title>Client2</title> </head> <body> <h1>SSO Demo client2 </h1> <a href= "http://127.0.0.1:8080/client1/index.html" >访问Client1</a> </body> </html> |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义