一个帐号,同时多处登陆的问题(apache security)

本文介绍如何使用Spring Security来控制用户的并发会话,防止一个账户在多处同时登录。通过配置ConcurrentSessionFilter及相关的Bean,可以实现限制用户重复登录的功能,即后登录的用户将挤掉先前登录的会话。
找到的相关资料


急答:一个帐号,同时多处登陆的问题(apache security)

http://www.blogjava.net/beyondwcm/archive/2009/05/08/269545.html

定制的Spring Security(Acegi)的并发会话过滤器(ConcurrentSessionFilter)的编码过程


springsecurity扩展自定义会话管理(一)控制用户重复登陆

springsecurity有控制单账号只能在一个地方登陆的功能,后登陆用户将踢掉前登陆用户;或者限制账号重复登陆,一个账号没有退出,另外一个人想用这个账号就登陆不上

主要涉及的类如下(我自己的山寨理解):
HttpSessionEventPublisher 监听session创建和销毁
ConcurrentSessionFilter 每次有http请求时校验,看你当前的session是否是过期的
SessionRegistryImpl 存放session中的信息,并做处理
ConcurrentSessionControllerImpl 用户登入登出的控制
SessionInformation 存储session中信息的model

先实现springsecurity文档上的,限制用户重复登陆,后登陆用户将前登陆用户冲掉 ,只需要在xml中配置如下:
<authentication-manager alias="authenticationManager"
session-controller-ref="currentController" />
<beans:bean id="concurrentSessionFilter" class="org.springframework.security.concurrent.ConcurrentSessionFilter">
<custom-filter position="CONCURRENT_SESSION_FILTER" />
<beans:property name="sessionRegistry" ref="sessionRegistry" />
<!-- 踢出的用户转向的页面-->
<beans:property name="expiredUrl" value="/user/user.action" />
<beans:property name="logoutHandlers">
<beans:list>
<beans:bean class="org.springframework.security.ui.logout.SecurityContextLogoutHandler" />
<beans:bean
class="org.springframework.security.ui.rememberme.TokenBasedRememberMeServices">
<beans:property name="key" value="e37f4b31-0c45-11dd-bd0b-0800200c9a66"/>
<beans:property name="userDetailsService" ref="userDetailsService"/>
</beans:bean>
</beans:list>
</beans:property>
</beans:bean>
<beans:bean id="sessionRegistry" class="org.springframework.security.concurrent.SessionRegistryImpl" />
<beans:bean id="currentController"
class="org.springframework.security.concurrent.ConcurrentSessionControllerImpl">
<beans:property name="sessionRegistry" ref="sessionRegistry" />
<!-- true限制不允许第二个用户登录,false第二个登陆用户踢掉前一个登陆用户 -->
<beans:property name="exceptionIfMaximumExceeded" value="false" />
<!-- 等第二种情况是,允许同时多少个用户同时登陆 -->
<beans:property name="maximumSessions" value="2"/>
</beans:bean>

其中web.xml还需要添加:
<!-- 登入和登出时对SessionRegistryImpl进行处理 -->
<listener>
<listener-class>org.springframework.security.ui.session.HttpSessionEventPublisher</listener-class>
</listener>


随着业务的发展,公司部署的网站越来越多,有必要整合多个网站,采用统一的用户验证。目前想到的解决方案,主要有以下几种.
1.sso解决方案,部署独立的用户验证系统,利用cookie的保存登录ticket的基本思路,网上有很多种解决方案,有的过于复杂,需要配置很多东西,有的过于局限,只支持单一的语言。不知道各位有哪些比较好的解决方案,适合项目不是太多,配置不是太烦,支持多中语言,例如java和php等。
2.开放api,oauth验证方式,不仅适合对内项目,而且可以对外验证。感觉好像很好的,而且我们iteye论坛也有ROP等实现方式,自我感觉这种方式比较很不错.
3.统一的验证窗口,所有的登录都提交到统一的登录窗口,由其负责处理。实现最简单,不过,需要同一级域名。
不知,各位大牛们有什么比较好的意见?

个人感觉第一种比较不错,你可以采用单点登录在做到这点,一台服务器只作为验证服务器,随着项目的增多,每个项目的验证都可以使用这台验证服务器做单点登录,这也需要一个相同的登录界面,和第三种方案比较类似,不过需要注意的是,单点登录是可以跨域的(跨根域)
对于你的第三种方案,只要js比较牛的话,应该也是可以的,淘宝的不就是这样做的吗 ?


sso一般有两种实现,基于agent和proxy。

CAS是基于agent的


XMPP 简单研究
<think> 我们正在构建一个基于Spring Security 6和Spring Authorization Server 1.2.7的UAA(用户账户和认证)服务,该服务需要支持两种登录方式:微信扫码登录账号密码登录,并在登录成功后生成用于访问后续Web微服务的令牌。 步骤概述: 1. 搭建基础Spring Boot项目,引入Spring Security、Spring Authorization Server等相关依赖。 2. 配置授权服务器,包括端点的配置、令牌的管理、客户端信息配置等。 3. 实现账号密码登录:使用Spring Security的默认表单登录或自定义表单登录。 4. 扩展微信扫码登录:自定义授权类型(grant type)和相应的认证流程。 5. 将两种登录方式集成到授权服务器,并能够生成标准的OAuth2令牌(如JWT)。 详细步骤: 一、项目搭建与依赖 使用Spring Initializr(start.spring.io)创建一个Spring Boot项目,选择以下依赖: - Spring Web - Spring Security - Spring Authorization Server (注意选择版本1.2.7,因为Spring Boot 3.x默认的Authorization Server版本可能更高,需要手动调整版本号) 在pom.xml中明确指定Spring Authorization Server的版本(1.2.7)和Spring Security的版本(6.x,由于Spring Boot 3.x会管理Spring Security版本,通常只需指定boot版本即可,但需注意兼容性)。 二、配置授权服务器 1. 配置授权服务器端点(AuthorizationServerEndpointsConfigurer): - 配置令牌生成服务(AuthorizationServerTokenServices)和令牌存储(TokenStore),这里可以使用JWT令牌。 - 配置用户详情服务(UserDetailsService)用于账号密码登录的认证。 2. 配置客户端信息(RegisteredClientRepository): - 注册一个个客户端,指定客户端ID、密钥、授权类型(authorization_code, password, refresh_token等)和重定向URI。 注意:Spring Authorization Server 1.2.x的配置方式与旧版本(如0.x)有较大区别,不再使用@EnableAuthorizationServer注解,而是通过配置一个AuthorizationServerConfig的Bean。 三、账号密码登录 账号密码登录可以使用OAuth2的密码模式(password grant)或者通过授权码模式(authorization code)结合表单登录实现。但考虑到安全性和OAuth2的最佳实践,推荐使用授权码模式(authorization code)结合表单登录。 在Spring Security中,我们可以配置一个标准的登录表单,用户通过表单提交用户名和密码,然后由Spring Security的认证管理器进行认证。认证成功后,授权服务器会生成授权码,然后客户端使用授权码交换令牌。 四、扩展微信扫码登录 微信扫码登录需要自定义一个授权类型(grant type),比如我们可以命名为`wechat`。这需要以下步骤: 1. 创建自定义的授权类型(GrantType): - 定义一个新的GrantType,例如`GrantType.WECHAT`,值为"wechat"。 2. 创建自定义的认证令牌(AuthenticationToken): - 创建`WechatAuthenticationToken`,它应该继承自`AbstractAuthenticationToken`,并包含微信登录所需的参数(如临时授权码code)。 3. 创建认证提供者(AuthenticationProvider): - 实现`WxAuthenticationProvider`,用于处理`WechatAuthenticationToken`类型的认证。在该提供者中,使用微信的code调用微信接口获取用户信息(如openid),然后根据openid查询本地用户系统,如果用户存在则认证成功,否则可以注册新用户或返回错误。 4. 将自定义的认证提供者注册到认证管理器(AuthenticationManager)中。 5. 在授权服务器端点配置中注册自定义的授权类型(TokenGranter): - 需要自定义一个`WechatTokenGranter`,它应该继承`AbstractTokenGranter`,并在其中使用自定义的`AuthenticationProvider`。 6. 在客户端配置中允许使用`wechat`授权类型。 五、生成令牌 无论哪种登录方式,认证成功后,授权服务器都会使用`AuthorizationServerTokenServices`生成令牌(通常为JWT)。令牌中会包含用户信息和权限。 六、整合 将上述配置整合到授权服务器配置类中,确保两种登录方式都能正常工作。 代码示例(部分关键点): 1. 自定义微信认证令牌: ```java public class WechatAuthenticationToken extends AbstractAuthenticationToken { private final String code; // 微信临时授权码 private Object principal; // 认证成功后填充用户信息 public WechatAuthenticationToken(String code) { super(null); this.code = code; setAuthenticated(false); } // 其他方法... } ``` 2. 自定义微信认证提供者: ```java @Component public class WechatAuthenticationProvider implements AuthenticationProvider { @Autowired private WxAuthService wxAuthService; // 调用微信接口的服务 @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { WechatAuthenticationToken token = (WechatAuthenticationToken) authentication; String code = token.getCode(); // 调用微信服务获取用户信息(如openid) UserDetails userDetails = wxAuthService.wxAuth(code); // 这里返回UserDetails,如果用户不存在,可以在这里创建用户 // 创建已认证的Token WechatAuthenticationToken authenticatedToken = new WechatAuthenticationToken(userDetails, userDetails.getAuthorities()); authenticatedToken.setDetails(token.getDetails()); return authenticatedToken; } @Override public boolean supports(Class<?> authentication) { return WechatAuthenticationToken.class.isAssignableFrom(authentication); } } ``` 3. 自定义TokenGranter: ```java public class WechatTokenGranter extends AbstractTokenGranter { private static final GrantType WECHAT_GRANT_TYPE = new GrantType("wechat"); private final AuthenticationManager authenticationManager; protected WechatTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices, ClientRegistrationService clientRegistrationService, OAuth2AuthorizationService authorizationService) { super(tokenServices, clientRegistrationService, authorizationService, WECHAT_GRANT_TYPE); this.authenticationManager = authenticationManager; } @Override protected OAuth2Authentication getOAuth2Authentication(Client client, TokenRequest tokenRequest) { Map<String, String> parameters = tokenRequest.getRequestParameters(); // 从请求参数中获取微信授权码 String code = parameters.get("code"); Authentication userAuth = new WechatAuthenticationToken(code); userAuth = authenticationManager.authenticate(userAuth); OAuth2Authentication authentication = new OAuth2Authentication(tokenRequest.createOAuth2Request(client), userAuth); return authentication; } } ``` 4. 在授权服务器配置中注册自定义TokenGranter: ```java @Configuration @EnableAuthorizationServer public class AuthorizationServerConfig { // ... 其他配置 @Autowired private AuthenticationManager authenticationManager; @Autowired private WechatAuthenticationProvider wechatAuthenticationProvider; @Bean public TokenGranter tokenGranter(AuthorizationServerEndpoints endpoints) { List<TokenGranter> tokenGranters = new ArrayList<>(); tokenGranters.add(new AuthorizationCodeTokenGranter(endpoints.getTokenServices(), endpoints.getAuthorizationCodeServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory())); tokenGranters.add(new RefreshTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory())); // 添加其他内置的TokenGranter... // 添加自定义的微信TokenGranter tokenGranters.add(new WechatTokenGranter( authenticationManager, endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getAuthorizationService() )); return new CompositeTokenGranter(tokenGranters); } // 同时需要将自定义的AuthenticationProvider加入AuthenticationManager @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) { auth.authenticationProvider(wechatAuthenticationProvider); // 其他提供者,如DaoAuthenticationProvider用于密码登录 } } ``` 注意:以上代码仅为示例,实际开发中需要根据具体情况进行调整。 七、测试 使用Postman等工具分别测试账号密码登录和微信登录的令牌获取。 注意事项: - 微信登录需要到微信开放平台注册应用,获取appid和appsecret。 - 在自定义的微信认证服务(WxAuthService)中,需要调用微信的接口(如https://api.weixin.qq.com/sns/oauth2/access_token)用code换取access_token和openid。 通过以上步骤,我们可以搭建一个支持微信扫码登录账号密码登录的UAA服务,并为后续的Web微服务提供令牌。 引用说明: - 扩展自定义授权类型参考了引用[1]和[2]的思路。 - 令牌的生成与管理参考了引用[3]。 - 整个授权服务器的搭建可以参考引用[4]中的经验。 [^1]: Spring Security OAuth2 扩展微信授权登录。 [^2]: 定义与微信认证的service接口。 [^3]: createAccessToken()方法。 [^4]: SpringCloud+Spring Security+OAuth2授权登录(三)。 §§相关问题§§ 1. 如何在Spring Authorization Server中配置JWT令牌? 2. 如何自定义OAuth2令牌中的声明(claims)? 3. 微信扫码登录中,如何将微信用户与本地用户系统关联? 4. 如何实现单点登录(SSO)和统一登出? </think>### 使用 Spring Security 6 和 Spring Authorization Server 1.2.7 搭建 UAA 服务 以下是实现支持微信扫码登录 + 账号密码登录的 UAA 服务步骤: --- ### 一、基础环境搭建 **1. Maven 依赖** ```xml <dependencies> <!-- Spring Authorization Server --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-authorization-server</artifactId> <version>1.2.7</version> </dependency> <!-- Spring Security 6 --> <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.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency> </dependencies> ``` **2. 授权服务器配置** ```java @Configuration @EnableAuthorizationServer public class AuthServerConfig { @Bean public RegisteredClientRepository registeredClientRepository() { RegisteredClient client = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("web-client") .clientSecret("{bcrypt}$2a$10$...") // BCrypt 加密 .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.PASSWORD) .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .redirectUri("http://localhost:8080/login/oauth2/code/web") .scope("user_info") .build(); return new InMemoryRegisteredClientRepository(client); } @Bean public JWKSource<SecurityContext> jwkSource() { RSAKey rsaKey = generateRsaKey(); JWKSet jwkSet = new JWKSet(rsaKey); return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); } } ``` --- ### 二、实现微信扫码登录 **1. 自定义微信认证令牌** ```java public class WechatAuthenticationToken extends AbstractAuthenticationToken { private final String code; // 微信临时授权码 public WechatAuthenticationToken(String code) { super(null); this.code = code; super.setAuthenticated(false); } // 实现 getCredentials() 和 getPrincipal() } ``` **2. 微信认证 Provider** ```java @Component public class WechatAuthenticationProvider implements AuthenticationProvider { @Autowired private WxAuthService wxAuthService; @Override public Authentication authenticate(Authentication auth) { WechatAuthenticationToken token = (WechatAuthenticationToken) auth; String code = token.getCode(); // 调用微信API获取用户信息 UserDetails user = wxAuthService.wxAuth(code); return new WechatAuthenticationToken(user, user.getAuthorities()); } @Override public boolean supports(Class<?> authentication) { return WechatAuthenticationToken.class.isAssignableFrom(authentication); } } ``` **3. 微信认证接口服务** ```java @Service public class WxAuthServiceImpl implements WxAuthService { private static final String WX_API = "https://api.weixin.qq.com/sns/oauth2/access_token"; @Override public UserDetails wxAuth(String code) { // 1. 调用微信API获取access_token String response = HttpClientUtils.get(WX_API + "?appid=APPID&secret=SECRET&code=" + code); // 2. 解析微信用户信息 WechatUserInfo userInfo = parseUserInfo(response); // 3. 查询或创建本地用户 return userRepository.findByOpenId(userInfo.getOpenid()) .orElseGet(() -> createUser(userInfo)); } } ``` --- ### 三、整合双登录方式 **1. 安全配置** ```java @Configuration public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeRequests(auth -> auth .antMatchers("/login/wechat").permitAll() .anyRequest().authenticated() ) .formLogin(withDefaults()) // 启用密码登录 .oauth2Login(oauth2 -> oauth2 // 启用OAuth2登录 .loginPage("/login") .authorizationEndpoint(authorization -> authorization .baseUri("/oauth2/authorize") ) ); return http.build(); } @Autowired public void bindAuthenticationProvider(AuthenticationManagerBuilder auth, WechatAuthenticationProvider wechatProvider) { auth.authenticationProvider(wechatProvider); } } ``` **2. 微信登录端点** ```java @RestController @RequestMapping("/login") public class AuthController { @PostMapping("/wechat") public ResponseEntity<?> wechatLogin(@RequestParam String code) { Authentication auth = new WechatAuthenticationToken(code); Authentication result = authenticationManager.authenticate(auth); // 生成OAuth2访问令牌 OAuth2AccessToken token = tokenServices.createAccessToken( new OAuth2Authentication(tokenRequest, result) ); return ResponseEntity.ok(token); } } ``` --- ### 四、令牌生成与验证 1. **令牌生成流程** - 微信登录:用户扫码 → 获取code → 认证 → 生成 `WechatAuthenticationToken` → 转换为 `OAuth2Authentication` → 生成JWT - 密码登录:提交表单 → `DaoAuthenticationProvider` 认证 → 生成 `UsernamePasswordAuthenticationToken` → 转换为 `OAuth2Authentication` → 生成JWT 2. **令牌验证** ```yaml # application.yml spring: security: oauth2: resourceserver: jwt: issuer-uri: http://localhost:9000 ``` --- ### 五、微服务调用验证 客户端在请求头中添加令牌: ```http GET /api/resource HTTP/1.1 Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIg... ``` --- ### 注意事项 1. **微信开放平台配置** 需注册应用并配置授权回调域名:`http://your-uaa-service/login/wechat` 2. **用户数据同步** 首次微信登录时创建本地用户记录,绑定`openid` 3. **令牌增强** 自定义 `JwtEncoder` 在令牌中添加用户信息: ```java token.claims(claims -> claims .claim("user_id", user.getId()) .claim("avatar", user.getAvatar()) ); ``` 4. **安全加固** - 启用 CSRF 保护 - 配置 HTTPS - 令牌有效期缩短(建议 access_token 2小时) 该方案实现了: - ✅ 标准 OAuth2 授权服务器 - ✅ 微信扫码登录集成 - ✅ 账号密码登录支持 - ✅ JWT 令牌颁发 - ✅ 微服务资源保护 [^1]: Spring Security OAuth2 扩展微信授权登录 [^2]: 微信认证接口定义 [^3]: Spring Authorization Server 令牌生成机制
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值