如何在运行时将新用户添加到 Spring Security
Posted
技术标签:
【中文标题】如何在运行时将新用户添加到 Spring Security【英文标题】:How to add new user to Spring Security at runtime 【发布时间】:2015-11-21 13:30:07 【问题描述】:我通过 Hibernate 将用户保存在数据库表中,并且我正在使用 Spring Security 进行身份验证:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.authentication.builders.*;
import org.springframework.security.config.annotation.web.configuration.*;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
这很好用,但有一点 - 用户在服务器启动期间被加载。我需要编写在运行时将新用户添加到 Spring Security 的方法 RegisterUser(User user)。这种方法应该只关注这个任务。我不知道如何开始实现此功能,所以感谢您的任何建议! ;)
Ofc 用户具有登录名、密码、角色字符串等字段...
请不要使用 Spring MVC 发布解决方案。本系统是使用 Spring Web Boost 和 Spring Security Boost 4.0.x 版本的 RESTful 应用程序
【问题讨论】:
那里有 Spring Security + Hibernate 的例子,给它一个搜索。例如:mkyong.com/spring-security/… 请问您找到解决问题的方法了吗?可以发布代码吗? 【参考方案1】:如果用户正在注册,您可能希望将用户存储在数据库中而不是内存中:)
为用户创建authorities
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
实例化用户(使用实现UserDetails的类)
UserDetails user = new User("user@example.com", passwordEncoder.encode("s3cr3t"), authorities);
将用户保存在有用的地方。 JdbcUserDetailsManager 可以轻松地将用户保存到数据库中。
userDetailsManager.createUser(user);
创建UsernamePasswordAuthenticationToken
Authentication authentication = new UsernamePasswordAuthenticationToken(user, null, authorities);
将身份验证添加到SecurityContext
SecurityContextHolder.getContext().setAuthentication(authentication);
【讨论】:
我在哪里创建这个用户和SecurityContext? @PhilippJahoda SecurityContext 由 Spring 创建。在从用户获取信息(例如他们想要的用户名和密码)之后,您可能希望在注册逻辑中创建用户,可能在控制器使用的服务层中 感谢您的回复。我正在使用 spring 提供的 /oauth/token URL,这不正确吗?我在哪里可以访问此网址提供的用户名和密码?【参考方案2】:使用此代码为当前用户添加权限:
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_NEWUSERROLE');
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(
SecurityContextHolder.getContext().getAuthentication().getPrincipal(),
SecurityContextHolder.getContext().getAuthentication().getCredentials(),
authorities)
);
【讨论】:
【参考方案3】:您可以使用Spring Data JPA 来创建用户。
@Repository
public interface UserRepository extends JpaRepository<User, Long>
用法:
User user = new User();
userRepository.save(user);
如何验证上述用户:
-
创建自定义
AuthenticationProvider
,从您的数据库中选择用户数据并进行身份验证:
@Component
public class MyAuthenticationProvider implements AuthenticationProvider
@Autowired
private UserRepository userRepository;
@Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException
final UsernamePasswordAuthenticationToken upAuth = (UsernamePasswordAuthenticationToken) authentication;
final String name = (String) authentication.getPrincipal();
final String password = (String) upAuth.getCredentials();
final String storedPassword = userRepository.findByName(name).map(User::getPassword)
.orElseThrow(() -> new BadCredentialsException("illegal id or passowrd"));
if (Objects.equals(password, "") || !Objects.equals(password, storedPassword))
throw new BadCredentialsException("illegal id or passowrd");
final Object principal = authentication.getPrincipal();
final UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
principal, authentication.getCredentials(),
Collections.emptyList());
result.setDetails(authentication.getDetails());
return result;
...
-
使用
WebSecurityConfigurerAdapter
配置以上AuthenticationProvider
:
@EnableWebSecurity
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter
@Autowired
private MyAuthenticationProvider authProvider;
@Override
protected void configure(final HttpSecurity http) throws Exception
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
http.authenticationProvider(authProvider);
参考:
Spring Security Architecture complete code sample【讨论】:
我一直在试图解决这个问题。UserRepository
是在哪里构造的?
UserRepository 由 Spring Boot 自动构建。 Core concepts 部分和Spring Data JPA Repositories 部分可能会对您有所帮助。【参考方案4】:
首先,创建一个用于注册用户的表单。
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!doctype html>
<html lang="en">
<head>
<title>Register New User Form</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Reference Bootstrap files -->
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.0/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<div>
<div id="loginbox" style="margin-top: 50px;"
class="mainbox col-md-3 col-md-offset-2 col-sm-6 col-sm-offset-2">
<div class="panel panel-primary">
<div class="panel-heading">
<div class="panel-title">Register New User</div>
</div>
<div style="padding-top: 30px" class="panel-body">
<!-- Registration Form -->
<form:form action="$pageContext.request.contextPath/register/processRegistrationForm"
modelAttribute="user"
class="form-horizontal">
<!-- Place for messages: error, alert etc ... -->
<div class="form-group">
<div class="col-xs-15">
<div>
<!-- Check for registration error -->
<c:if test="$registrationError != null">
<div class="alert alert-danger col-xs-offset-1 col-xs-10">
$registrationError
</div>
</c:if>
</div>
</div>
</div>
<!-- User name -->
<div style="margin-bottom: 25px" class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
<form:input path="username" placeholder="username" class="form-control" />
</div>
<!-- Password -->
<div style="margin-bottom: 25px" class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></span>
<form:password path="password" placeholder="password" class="form-control" />
</div>
<!-- Register Button -->
<div style="margin-top: 10px" class="form-group">
<div class="col-sm-6 controls">
<button type="submit" class="btn btn-primary">Register</button>
</div>
</div>
</form:form>
</div>
</div>
</div>
</div>
</body>
</html>
并在控制器中编写一个action方法来获取表单信息并将用户保存到数据库中。
@Controller
@RequestMapping( "/register" )
public class RegistrationController
@Autowired
private UserDetailsManager userDetailsManager;
private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
private Logger logger = Logger.getLogger( getClass().getName() );
@InitBinder
public void initBinder( WebDataBinder dataBinder )
StringTrimmerEditor stringTrimmerEditor = new StringTrimmerEditor( true );
dataBinder.registerCustomEditor( String.class, stringTrimmerEditor );
@PostMapping( "/processRegistrationForm" )
public String processRegistrationForm( @Valid @ModelAttribute( "user" ) com.exmaple.spring_demo.entity.User user, BindingResult theBindingResult, Model theModel )
String userName = user.getUsername();
logger.info( "Processing registration form for: " + userName );
// form validation
if ( theBindingResult.hasErrors() )
theModel.addAttribute( "user", new com.exmaple.spring_demo.entity.User() );
theModel.addAttribute( "registrationError", "User name/password can not be empty." );
logger.warning( "User name/password can not be empty." );
return "security/user/registration-form";
// check the database if user already exists
boolean userExists = doesUserExist( userName );
if ( userExists )
theModel.addAttribute( "user", new com.exmaple.spring_demo.entity.User() );
theModel.addAttribute( "registrationError", "User name already exists." );
logger.warning( "User name already exists." );
return "security/user/registration-form";
//
// whew ... we passed all of the validation checks!
// let's get down to business!!!
//
// encrypt the password
String encodedPassword = passwordEncoder.encode( user.getPassword() );
// prepend the encoding algorithm id
encodedPassword = "bcrypt" + encodedPassword;
// give user default role of "ROLE_USER"
List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList( "ROLE_USER" );
// create user object (from Spring Security framework)
User tempUser = new User( userName, encodedPassword, authorities );
// save user in the database
userDetailsManager.createUser( tempUser );
logger.info( "Successfully created user: " + userName );
return "security/user/registration-confirmation";
@GetMapping( "/showRegistrationForm" )
public String showMyLoginPage( Model theModel )
theModel.addAttribute( "user", new com.exmaple.spring_demo.entity.User() );
return "security/user/registration-form";
private boolean doesUserExist( String userName )
logger.info( "Checking if user exists: " + userName );
// check the database if the user already exists
boolean exists = userDetailsManager.userExists( userName );
logger.info( "User: " + userName + ", exists: " + exists );
return exists;
现在,在 Spring Configuration 中定义 DataSource。
package com.exmaple.spring_demo.config;
import java.beans.PropertyVetoException;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.provisioning.UserDetailsManager;
import com.mchange.v2.c3p0.ComboPooledDataSource;
@Configuration
@EnableWebSecurity
@ComponentScan( "com.exmaple.spring_demo.config" )
@PropertySource( "classpath:persistence-mysql.properties" )
public class SecurityConfigJDBC extends WebSecurityConfigurerAdapter
/**
* set up variable to hold the properties
*/
@Autowired
private Environment env;
// define a bean for our security datasource
@Bean
public DataSource dataSource()
// create connection pool
ComboPooledDataSource securityDataSource = new ComboPooledDataSource();
// set the jdbc driver class
try
securityDataSource.setDriverClass( env.getProperty( "jdbc.driver" ) );
catch ( PropertyVetoException exc )
throw new RuntimeException( exc );
// log the connection props
// for sanity's sake, log this info
// just to make sure we are REALLY reading data from properties file
System.out.println( ">>> jdbc.url=" + env.getProperty( "jdbc.url" ) );
System.out.println( ">>> jdbc.user=" + env.getProperty( "jdbc.user" ) );
// set database connection props
securityDataSource.setJdbcUrl( env.getProperty( "jdbc.url" ) );
securityDataSource.setUser( env.getProperty( "jdbc.user" ) );
securityDataSource.setPassword( env.getProperty( "jdbc.password" ) );
// set connection pool props
securityDataSource.setInitialPoolSize( getIntProperty( "connection.pool.initialPoolSize" ) );
securityDataSource.setMinPoolSize( getIntProperty( "connection.pool.minPoolSize" ) );
securityDataSource.setMaxPoolSize( getIntProperty( "connection.pool.maxPoolSize" ) );
securityDataSource.setMaxIdleTime( getIntProperty( "connection.pool.maxIdleTime" ) );
return securityDataSource;
@Bean
public UserDetailsManager userDetailsManager()
JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager();
jdbcUserDetailsManager.setDataSource( dataSource() );
return jdbcUserDetailsManager;
@Override
protected void configure( AuthenticationManagerBuilder auth ) throws Exception
auth.jdbcAuthentication().dataSource( dataSource() );
@Override
protected void configure( HttpSecurity http ) throws Exception
http.authorizeRequests()
.antMatchers( "/home/**" ).hasRole( "USER" )
.antMatchers( "/manager/**" ).hasRole( "MANAGERS" )
.antMatchers( "/admin/**" ).hasRole( "ADMIN" )
.and()
.formLogin()
.loginPage( "/showMyLoginPage" )
.loginProcessingUrl( "/authenticateTheUser" )// submit form data
.permitAll()
.and()
.logout().permitAll()
.and()
.exceptionHandling().accessDeniedPage( "/access-denied" )
.and()
.csrf()
.disable();
/**
* need a helper method
* read environment property and convert to int
*/
private int getIntProperty( String propName )
String propVal = env.getProperty( propName );
// now convert to int
int intPropVal = Integer.parseInt( propVal );
return intPropVal;
最后,在 src/main/resources/persistence-mysql.properties 中创建 JDBC 属性文件。
#
# JDBC connection properties
#
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_security_demo?useSSL=false
jdbc.user=springstudent
jdbc.password=springstudent
#
# Connection pool properties
#
connection.pool.initialPoolSize=5
connection.pool.minPoolSize=5
connection.pool.maxPoolSize=20
connection.pool.maxIdleTime=3000
UserDetailsService (JdbcDaoImpl) 的标准 JDBC 实现需要表来加载用户的密码、帐户状态(启用或禁用)和权限列表(角色)。您需要调整此架构以匹配您正在使用的数据库方言。
CREATE TABLE `authorities` (
`username` varchar(50) NOT NULL,
`authority` varchar(50) NOT NULL,
UNIQUE KEY `authorities_idx_1` (`username`,`authority`),
CONSTRAINT `authorities_ibfk_1`
FOREIGN KEY (`username`)
REFERENCES `users` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `users` (
`username` varchar(50) NOT NULL,
`password` varchar(50) NOT NULL,
`enabled` tinyint(1) NOT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
【讨论】:
以上是关于如何在运行时将新用户添加到 Spring Security的主要内容,如果未能解决你的问题,请参考以下文章
在运行时将新表重新加载/替换到 WPF DataGrid 中