我正在开发一个使用Spring Security OAuth2保护少量REST API的应用程序。身份验证工作正常。现在我想为每个帐户实现单点登录功能。一旦用户成功使用他的凭据从一个设备登录然后它不可能从其他设备登录相同的使用用户。那时用户只允许一次登录。如果他想登录其他设备,他应该从已经登录的设备注销。如何在春天安全oauth.Below是我的代码。
spring-security.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:oauth="http://www.springframework.org/schema/security/oauth2"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:sec="http://www.springframework.org/schema/security" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd ">
<http pattern="/oauth/token" create-session="stateless" authentication-manager-ref="authenticationManager"
xmlns="http://www.springframework.org/schema/security" >
<intercept-url pattern="/oauth/token" access="IS_AUTHENTICATED_FULLY" method="POST" />
<anonymous enabled="false" />
<http-basic entry-point-ref="clientAuthenticationEntryPoint" />
<custom-filter ref="clientCredentialsTokenEndpointFilter" before="BASIC_AUTH_FILTER" />
<access-denied-handler ref="oauthAccessDeniedHandler" />
</http>
<http pattern="/protected/**" create-session="never" entry-point-ref="oauthAuthenticationEntryPoint"
xmlns="http://www.springframework.org/schema/security">
<anonymous enabled="false" />
<intercept-url pattern="/protected/**" method="GET" access="ROLE_APP" />
<!-- <intercept-url pattern="/resources/**" access="IS_AUTHENTICATED_FULLY" /> -->
<custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
<access-denied-handler ref="oauthAccessDeniedHandler" />
</http>
<http pattern="/logout" create-session="never"
entry-point-ref="oauthAuthenticationEntryPoint"
xmlns="http://www.springframework.org/schema/security">
<anonymous enabled="false" />
<intercept-url pattern="/logout" method="GET" />
<sec:logout invalidate-session="true" logout-url="/logout" success-handler-ref="logoutSuccessHandler" />
<custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />
<access-denied-handler ref="oauthAccessDeniedHandler" />
</http>
<bean id="logoutSuccessHandler" class="com.example.myproject.security.LogoutImpl" >
<property name="tokenstore" ref="tokenStore"></property>
</bean>
<bean id="oauthAuthenticationEntryPoint"
class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
<property name="exceptionTranslator" ref="myExceptionTranslator"></property>
</bean>
<bean id="myExceptionTranslator"
class="org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator">
</bean>
<bean id="clientAuthenticationEntryPoint"
class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
<property name="realmName" value="springsec/client" />
<property name="typeName" value="Basic" />
</bean>
<bean id="oauthAccessDeniedHandler"
class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler">
</bean>
<bean id="clientCredentialsTokenEndpointFilter"
class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
<property name="authenticationManager" ref="authenticationManager" />
</bean>
<authentication-manager alias="authenticationManager"
xmlns="http://www.springframework.org/schema/security">
<authentication-provider user-service-ref="clientDetailsUserService" />
</authentication-manager>
<bean id="clientDetailsUserService"
class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
<constructor-arg ref="clientDetails" />
</bean>
<bean id="clientDetails" class="com.example.myproject.service.ClientService"/>
<authentication-manager id="userAuthenticationManager"
xmlns="http://www.springframework.org/schema/security">
<authentication-provider user-service-ref="userService">
</authentication-provider>
</authentication-manager>
<bean id="userService"
class="com.example.myproject.service.UserService">
</bean>
<oauth:authorization-server
client-details-service-ref="clientDetails" token-services-ref="tokenServices">
<oauth:authorization-code />
<oauth:implicit/>
<oauth:refresh-token/>
<oauth:client-credentials />
<oauth:password authentication-manager-ref="userAuthenticationManager"/>
</oauth:authorization-server>
<oauth:resource-server id="resourceServerFilter"
resource-id="springsec" token-services-ref="tokenServices" />
<!-- <bean id="tokenStore"
class="org.springframework.security.oauth2.provider.token.InMemoryTokenStore" /> -->
<bean id="tokenStore"
class="org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore" >
<property name="authenticationKeyGenerator">
<bean class="com.example.myproject.service.UniqueAuthenticationKeyGenerator" />
</property>
</bean>
<bean id="tokenServices"
class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
<property name="tokenStore" ref="tokenStore" />
<property name="supportRefreshToken" value="true" />
<property name="accessTokenValiditySeconds" value="300000"></property>
<property name="clientDetailsService" ref="clientDetails" />
<property name="tokenEnhancer"><bean class="com.example.myproject.service.CustomTokenEnhancer" /></property>
</bean>
<sec:global-method-security
pre-post-annotations="enabled" proxy-target-class="true">
<!--you could also wire in the expression handler up at the layer of the
http filters. See https://jira.springsource.org/browse/SEC-1452 -->
<sec:expression-handler ref="oauthExpressionHandler" />
</sec:global-method-security>
<oauth:expression-handler id="oauthExpressionHandler" />
<oauth:web-expression-handler id="oauthWebExpressionHandler" />
</beans>
ClientService.java:
@Component
public class ClientService implements ClientDetailsService {
@Autowired
private OauthRepository oauthRepository;
@Override
public ClientDetails loadClientByClientId(String s) throws ClientRegistrationException{
BaseClientDetails clientDetails = oauthRepository.getByClientId(s);
return clientDetails;
}
}
UserService.java:
@Component
public class UserService implements UserDetailsService {
@Autowired
private OauthRepository oauthRepository;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
UserDetails user = oauthRepository.getByUsername(s);
return user;
}
}
OauthRepository.java:
@Repository
@Transactional
public class OauthRepository {
@Autowired
private SessionFactory sessionFactory;
@Autowired
private InMemoryTokenStore tokenStore;
private org.hibernate.Session getCurrentSession(){
return sessionFactory.getCurrentSession();
}
public UserDetails getByUsername(String username) {
MyUser user=new MyUser();
user.setUserName(username);
Query query=getCurrentSession().createQuery("FROM User WHERE userName=:usrName");
query.setParameter("usrName", username);
List<SiUser> getUser=query.list();
User act=getUser.get(0);
user.setPassword(act.getPassword());
user.setUserId(act.getUserId());
user.setAuthorities(getAuthorities(act.getUserId()));
return user;
}
public BaseClientDetails getByClientId(String clientId) {
System.out.println(" *** OauthRepository.getByClientId "+clientId);
Query query=getCurrentSession().createQuery("FROM OauthClientDetails WHERE clientId=:clientId");
query.setParameter("clientId", clientId);
List<OauthClientDetails> getClient=query.list();
OauthClientDetails oauthClient=getClient.get(0);
BaseClientDetails details = new BaseClientDetails();
details.setClientId(oauthClient.getClientId());
List<String> grantTypesList = Arrays.asList(oauthClient.getAuthorizedGrantTypes().split(","));
details.setAuthorizedGrantTypes(grantTypesList);
details.setClientSecret(oauthClient.getClientSecret());
return details;
}
/**
* Retrieves a collection of {@link GrantedAuthority} based on a numerical role
* @param role the numerical role
* @return a collection of {@link GrantedAuthority
*/
public Collection<GrantedAuthority> getAuthorities(Integer role) {
List<GrantedAuthority> authList = getGrantedAuthorities(getRoles(role));
return authList;
}
/**
* Converts a numerical role to an equivalent list of roles
* @param role the numerical role
* @return list of roles as as a list of {@link String}
*/
public List<String> getRoles(Integer role) {
List<String> roles = new ArrayList<String>();
Query query=getCurrentSession().createQuery("FROM UserRole WHERE userID=:userId");
query.setParameter("userId", role);
List<SiUserRole> getUser=query.list();
UserRole actRole=getUser.get(0);
roles.add(actRole.getRole());
return roles;
}
/**
* Wraps {@link String} roles to {@link SimpleGrantedAuthority} objects
* @param roles {@link String} of roles
* @return list of granted authorities
*/
public static List<GrantedAuthority> getGrantedAuthorities(List<String> roles) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (String role : roles) {
authorities.add(new GrantedAuthorityImpl(role));
}
return authorities;
}
}
servlet-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">
<!-- @author Nagesh.Chauhan(neel4soft@gmail.com) -->
<context:annotation-config />
<context:component-scan base-package="com.example.myproject" />
<mvc:annotation-driven />
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- one of the properties available; the maximum file size in bytes -->
<property name="maxUploadSize" value="1000000000" />
</bean>
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/MyDatabase"/>
<property name="username" value="username"/>
<property name="password" value="password"/>
<property name="validationQuery" value="SELECT 1"/>
</bean>
<!-- Hibernate Session Factory -->
<bean id="mySessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<property name="packagesToScan">
<array>
<value>com.example.myproject</value>
</array>
</property>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.MySQLDialect
</value>
</property>
</bean>
<!-- Hibernate Transaction Manager -->
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="mySessionFactory"/>
</bean>
<!-- Activates annotation based transaction management -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>