I have implemented my user-storage-provider like this:
public class UserStorageProvider implements org.keycloak.storage.UserStorageProvider, UserLookupProvider, CredentialInputValidator {
public UserStorageProvider(KeycloakSession session) {
this.session = session;
}
public UserStorageProvider(KeycloakSession session, ComponentModel model) {
this.session = session;
this.model = model;
}
protected KeycloakSession session;
protected ComponentModel model;
#Override
public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) {
return true;
}
#Override
public boolean supportsCredentialType(String credentialType) {
return true;
}
#Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
return true;
}
#Override
public UserModel getUserByUsername(String username, RealmModel realm) {
//here to fetch user from my DB.
return null;
}
#Override
public UserModel getUserById(String id, RealmModel realm) {
StorageId storageId = new StorageId(id);
String username = storageId.getExternalId();
return getUserByUsername(username, realm);
}
#Override
public UserModel getUserByEmail(String email, RealmModel realm) {
return null;
}
public void close() {
}
}
and its factory class:
public class UserStorageProviderFactory implements org.keycloak.storage.UserStorageProviderFactory<UserStorageProvider> {
public static final String PROVIDER_NAME = "user-provider";
#Override
public String getHelpText() {
return "JPA Example User Storage Provider";
}
#Override
public List<ProviderConfigProperty> getConfigProperties() {
return null;
}
#Override
public UserStorageProvider create(KeycloakSession keycloakSession) {
return new UserStorageProvider(keycloakSession);
}
#Override
public UserStorageProvider create(KeycloakSession keycloakSession, ComponentModel componentModel) {
UserStorageProvider provider = new UserStorageProvider(keycloakSession, componentModel);
provider.session = keycloakSession;
provider.model = componentModel;
return provider;
}
#Override
public String getId() {
return PROVIDER_NAME;
}
#Override
public void init(Config.Scope config) {
}
#Override
public void postInit(KeycloakSessionFactory factory) {
}
#Override
public void close() {
}
}
and the org.keycloak.storage.UserStorageProviderFactory file is located on META-INF/services/ and the its content is:
com.kian.neshan.userfederation.UserStorageProviderFactory
So I make the jar by mvn clean package and put it in deplyment folder of keycloak but when I go to admin panel, my provider is not added to user-federation option
Where is wrong?
The whole of my module is correct.
The problem is the local-keycloak-docker-image shuold be removed after any changes of the module
Keycloak is started by docker-compose and its images is built by Dockerfile, so after any changes of my module, the local-keycloak-image should be removed by sudo docker rmi [local-image-name] to rebuild the image to get the newest my jar file and put it to deployment folder of local-docker-image.
Related
I'm calling the findUserByUsername() method to get the name field in the User entity and I'm wondering if there's any better to do it without having to execute an addional query
AuthenticationController.java
#PostMapping("/login")
public ResponseEntity<AuthenticationResponse> login (#RequestBody AuthenticationRequest userLogin) {
try {
Authentication authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(userLogin.username(), userLogin.password()));
String token = tokenService.generateToken(authentication);
Optional<User> user = userRepository.findByUsername(authentication.getName());
AuthenticationResponse response = new AuthenticationResponse(user.get().getName(), token);
return ResponseEntity.ok().body(response);
} catch (BadCredentialsException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}
SecurityUser.java
public class SecurityUser implements UserDetails {
private final User user;
public SecurityUser (User user) {
this.user = user;
}
#Override
public String getUsername() {
return user.getUsername();
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user
.getRoles()
.stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toSet());
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
#Override
public String toString() {
return "SecurityUser{" +
"user=" + user +
'}';
}
}
Depending on how your security configuration is set up, you can use authentication.getName(), as it usually maps to the username field. This would be the case with formLogin() for example, which uses the DaoAuthenticationProvider under the covers.
I am trying to implement method level security using spring security. I have annotated 2 separate methods with the #PreAuthorize annotation. In this example, I have 2 users ADMIN and USER. And I have restricted 2 methods both to each of the users. When I try logging in as USER I am able to access both the endpoint restricted to USER (getSomeTextForUser()) as well as to ADMIN(getSomeTextForAdmin()). So this is definitely not right and after viewing multiple tutorials I have not seen the error in my ways.
Expected behavior: person logged in as USER should get an error when trying to access the endpoint /test/admin since it calls getSomeTextForAdmin(). And the similar behavior should happen for the admin when calling /test/user since it calls getSomeTextForUser().
Main class
#SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
My controller class
#RestController
public class UserController {
#GetMapping("/")
public String home() {
return ("<h1> Welcome </h1>");
}
#GetMapping("/test/admin")
public String test() {
return getSomeTextForAdmin();
}
#GetMapping("/test/user")
public String test2() {
return getSomeTextForUser();
}
#PreAuthorize("hasRole('ROLE_ADMIN')")
public String getSomeTextForAdmin() {
return "For Admin Only!";
}
#PreAuthorize("hasRole('ROLE_USER')")
public String getSomeTextForUser() {
return "For User Only!";
}
}
The security configuration where I've enabled the prePost feature
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/test").hasAnyRole("ADMIN", "USER")
.antMatchers("/").permitAll()
.and().formLogin();
#Bean
public PasswordEncoder getPasswordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
My User details service where I've just placed some default users in memory on startup for testing.
#Repository
public class UserRepositoryImpl implements UserRepository {
Map<String, User> users = new HashMap<>();
public UserRepositoryImpl() {
createDefaultUsers();
}
#Override
public Optional<User> findByUserName(String userName) {
return Optional.of(users.get(userName));
}
private void createDefaultUsers() {
users.put("admin", new User("admin", "pass", "ADMIN"));
users.put("user", new User("user", "pass", "USER"));
}
}
MyUserDetails is here
public class MyUserDetails implements UserDetails {
private final String userName;
private final String password;
private final List<GrantedAuthority> authorities;
public MyUserDetails(User user) {
this.userName = user.getUserName();
this.password = user.getPassword();
this.authorities = Arrays.stream(user.getRoles().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
#Override
public String getPassword() {
return password;
}
#Override
public String getUsername() {
return userName;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
And the user class itself
#Entity
#Table(name = "User")
public class User {
public User(String userName, String password, String roles) {
this.userName = userName;
this.password = password;
this.roles = roles;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
public String getRoles() {
return roles;
}
public void setRoles(String roles) {
this.roles = roles;
}
private String userName;
private String password;
private boolean active;
private String roles;
}
First of all: why do you need this line in your configuration?
.antMatchers("/test").hasAnyRole("ADMIN", "USER")
You don't even have /test endpoint in your controller.
Second thing:
#RestController
public class UserController {
#GetMapping("/")
public String home() {
return ("<h1> Welcome </h1>");
}
#GetMapping("/test/admin")
public String test() {
return getSomeTextForAdmin();
}
#GetMapping("/test/user")
public String test2() {
return getSomeTextForUser();
}
#PreAuthorize("hasRole('ROLE_ADMIN')")
public String getSomeTextForAdmin() {
return "For Admin Only!";
}
#PreAuthorize("hasRole('ROLE_USER')")
public String getSomeTextForUser() {
return "For User Only!";
}
}
It shows you don't understand what Spring Proxy is. Unless you learn it, soon or later you will fall into problems.
I really encourge you to read about it but for now one takeaway to remember:
Annotated methods must be called from different class. In your case you call annotated methods from the same class and Spring doesn't care about any annotation.
You should use somtehing like this:
#Service
public class UserService {
#PreAuthorize("hasRole('ROLE_ADMIN')")
public String getSomeTextForAdmin() {
return "For Admin Only!";
}
#PreAuthorize("hasRole('ROLE_USER')")
public String getSomeTextForUser() {
return "For User Only!";
}
}
#RestController
public class UserController {
#Autowired
private UserService userService;
#GetMapping("/")
public String home() {
return ("<h1> Welcome </h1>");
}
#GetMapping("/test/admin")
public String test() {
return userService.getSomeTextForAdmin();
}
#GetMapping("/test/user")
public String test2() {
return userService.getSomeTextForUser();
}
}
I have nested properties class:
#ConfigurationProperties(prefix = "myapp", ignoreUnknownFields = false)
public class MyAppProperties implements Validator {
#Valid
private List<Server> servers = new ArrayList();
public MyAppProperties() {
}
public List<Server> getServers() {
return this.servers;
}
public void setServers(List<Server> servers) {
this.servers = servers;
}
#Override
public boolean supports(Class<?> clazz) {
return MyAppProperties.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
MyAppProperties properties = (MyAppProperties) target;
if (isEmpty(properties.getServers())) {
errors.rejectValue("servers", "myapp.servers", "Servers not provided");
}
}
public static class Server implements Validator {
private String name;
private String url;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
#Override
public boolean supports(Class<?> clazz) {
return Server.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Server server = (Server) target;
if (StringUtils.isBlank(server.getName())) {
errors.rejectValue("name", "server.name", "Server name not provided");
}
if (StringUtils.isBlank(server.getUrl())) {
errors.rejectValue("url", "server.url", "Server url not provided");
}
}
}
}
Now i want the validation to fire for both classes, but when i run with invalid values, the validation fires only for MyAppProperties but not for the list of Servers.
Am i doing something wrong, i'm not able to make this work. I want to use custom validator for both. Can anybody please help me make this work.
The invalid values that i pass are:
myapp.servers[0].name=
myapp.servers[0].url=
But this runs without any error
I am making a simple Social Media Website using Java Spring Boot. Now I want to add a profile edit page, where a logged in user can edit/update his profile data but other logged in users should not have access to it.
For example, there are two people John and Tom, John should be able to see only his profile edit page and Tom should see only his Profile edit page Only after login.
How to achieve this using Spring Security or by any other way ?
First of all you need to write BeanAccessor like following:
#Component
public class BeanAccessor implements ApplicationContextAware {
private static ApplicationContext context;
public static ObjectMapper getObjectMapper() {
return getBean(ObjectMapper.class);
}
public static <T> T getBean(Class<T> beanClass, Object... args) {
return context.getBean(beanClass, args);
}
private static <T> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
}
then we need to write new class for method security like:
#Component
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
private Object filterObject;
private Object returnObject;
private Object target;
public CustomMethodSecurityExpressionRoot(Authentication authentication) {
super(authentication);
}
CustomMethodSecurityExpressionRoot setTarget(Object target) {
this.target = target;
return this;
}
#Override
public void setFilterObject(Object filterObject) {
this.filterObject = filterObject;
}
#Override
public Object getFilterObject() {
return filterObject;
}
#Override
public void setReturnObject(Object returnObject) {
this.returnObject = returnObject;
}
#Override
public Object getReturnObject() {
return returnObject;
}
#Override
public Object getThis() {
return target;
}
}
finally we need custom method security expressinon handler:
#Component
public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
#Autowired
private CustomPermissionEvaluator customPermissionEvaluator;
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
#Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
final CustomMethodSecurityExpressionRoot root = BeanAccessor.getBean(CustomMethodSecurityExpressionRoot.class, authentication);
root.setPermissionEvaluator(customPermissionEvaluator);
root.setTrustResolver(this.trustResolver);
root.setRoleHierarchy(getRoleHierarchy());
root.setTarget(invocation.getThis());
return root;
}
}
now on your controller method yo can define #PreAuthorize("isProfileOwner(#id)") annotations your user profile show page method looks like :
#PreAuthorize("isProfileOwner(#id)")
#GetMapping("{id}")
public String show(#PathVariable("id") Long id, Model model) {
//omitted
}
everything okey but we need to write isProfileOwner() method to our CustomMethodSecurityExpressionRoot class like:
public boolean isProfileOwner(Long id) {
//add logic here and you are ready
}
also you can check this post
I have searched and can't find any examples that would show me a better way to do this, but in the Spring/Spring Boot code, there are generic builders but the builder itself seems to apply the properties programmatically. Here is some code trying to configure 2 Oracle Connection Pool Data Sources:
import oracle.ucp.jdbc.PoolDataSourceFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.sql.SQLException;
#Configuration
#EnableConfigurationProperties
#ConditionalOnClass(PoolDataSourceFactory.class)
public class PersistenceAutoConfiguration {
#Bean (name = "readWriteDataSource")
public DataSource getReadWriteDataSource() throws SQLException {
OracleUcpDataSourceProperties rwProperties = getReadWriteProperties();
return OracleUcpDataSourceBuilder.create()
.connectionFactoryClassName(rwProperties.getConnectionFactoryClassName())
.url(rwProperties.getUrl())
.user(rwProperties.getUser())
.password(rwProperties.getPassword())
.initialPoolSize(rwProperties.getInitialPoolSize())
.minPoolSize(rwProperties.getMinPoolSize())
.maxPoolSize(rwProperties.getMaxPoolSize())
.connectionWaitTimeout(rwProperties.getConnectionWaitTimeout())
.inactiveConnectionTimeout(rwProperties.getInactiveConnectionTimeout())
.maxIdleTime(rwProperties.getMaxIdleTime())
.build();
}
#Bean (name = "readOnlyDataSource")
public DataSource getReadOnlyDataSource() throws SQLException {
OracleUcpDataSourceProperties roProperties = getReadOnlyProperties();
return OracleUcpDataSourceBuilder.create()
.connectionFactoryClassName(roProperties.getConnectionFactoryClassName())
.url(roProperties.getUrl())
.user(roProperties.getUser())
.password(roProperties.getPassword())
.initialPoolSize(roProperties.getInitialPoolSize())
.minPoolSize(roProperties.getMinPoolSize())
.maxPoolSize(roProperties.getMaxPoolSize())
.connectionWaitTimeout(roProperties.getConnectionWaitTimeout())
.inactiveConnectionTimeout(roProperties.getInactiveConnectionTimeout())
.maxIdleTime(roProperties.getMaxIdleTime())
.build();
}
#ConfigurationProperties(prefix = "datasource.readwrite")
#Bean(name = "readWriteProperties")
protected OracleUcpDataSourceProperties getReadWriteProperties() {
return new OracleUcpDataSourceProperties();
}
#ConfigurationProperties(prefix = "datasource.readonly")
#Bean(name = "readOnlyProperties")
protected OracleUcpDataSourceProperties getReadOnlyProperties() {
return new OracleUcpDataSourceProperties();
}
}
and
public class OracleUcpDataSourceProperties {
private String connectionFactoryClassName;
private String url;
private String user;
private String password;
private int initialPoolSize;
private int minPoolSize;
private int maxPoolSize;
private int connectionWaitTimeout;
private int inactiveConnectionTimeout;
private int maxIdleTime;
private Boolean validateConnectionOnBorrow;
public String getConnectionFactoryClassName() {
return connectionFactoryClassName;
}
public void setConnectionFactoryClassName(String connectionFactoryClassName) {
this.connectionFactoryClassName = connectionFactoryClassName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getInitialPoolSize() {
return initialPoolSize;
}
public void setInitialPoolSize(int initialPoolSize) {
this.initialPoolSize = initialPoolSize;
}
public int getMinPoolSize() {
return minPoolSize;
}
public void setMinPoolSize(int minPoolSize) {
this.minPoolSize = minPoolSize;
}
public int getMaxPoolSize() {
return maxPoolSize;
}
public void setMaxPoolSize(int maxPoolSize) {
this.maxPoolSize = maxPoolSize;
}
public int getConnectionWaitTimeout() {
return connectionWaitTimeout;
}
public void setConnectionWaitTimeout(int connectionWaitTimeout) {
this.connectionWaitTimeout = connectionWaitTimeout;
}
public int getInactiveConnectionTimeout() {
return inactiveConnectionTimeout;
}
public void setInactiveConnectionTimeout(int inactiveConnectionTimeout) {
this.inactiveConnectionTimeout = inactiveConnectionTimeout;
}
public int getMaxIdleTime() {
return maxIdleTime;
}
public void setMaxIdleTime(int maxIdleTime) {
this.maxIdleTime = maxIdleTime;
}
public Boolean getValidateConnectionOnBorrow() {
return validateConnectionOnBorrow;
}
public void setValidateConnectionOnBorrow(Boolean validateConnectionOnBorrow) {
this.validateConnectionOnBorrow = validateConnectionOnBorrow;
}
}
and
import oracle.ucp.jdbc.PoolDataSource;
import oracle.ucp.jdbc.PoolDataSourceFactory;
import java.sql.SQLException;
public class OracleUcpDataSourceBuilder {
private PoolDataSource pds;
/**
* This will grab the pool factory and initialize it.
*/
public OracleUcpDataSourceBuilder() throws SQLException {
pds = PoolDataSourceFactory.getPoolDataSource();
}
public static OracleUcpDataSourceBuilder create() throws SQLException {
return new OracleUcpDataSourceBuilder();
}
public OracleUcpDataSourceBuilder connectionFactoryClassName(String connectionFactoryClassName) throws SQLException {
pds.setConnectionFactoryClassName(connectionFactoryClassName);
return this;
}
public OracleUcpDataSourceBuilder url(String url) throws SQLException {
pds.setURL(url);
return this;
}
public OracleUcpDataSourceBuilder user(String user) throws SQLException {
pds.setUser(user);
return this;
}
public OracleUcpDataSourceBuilder password(String password) throws SQLException {
pds.setPassword(password);
return this;
}
public OracleUcpDataSourceBuilder initialPoolSize(int initialPoolSize) throws SQLException {
pds.setInitialPoolSize(initialPoolSize);
return this;
}
public OracleUcpDataSourceBuilder minPoolSize(int minPoolSize) throws SQLException {
pds.setMinPoolSize(minPoolSize);
return this;
}
public OracleUcpDataSourceBuilder maxPoolSize(int maxPoolSize) throws SQLException {
pds.setMaxPoolSize(maxPoolSize);
return this;
}
public OracleUcpDataSourceBuilder connectionWaitTimeout(int connectionWaitTimeout) throws SQLException {
pds.setConnectionWaitTimeout(connectionWaitTimeout);
return this;
}
public OracleUcpDataSourceBuilder inactiveConnectionTimeout(int inactiveConnectionTime) throws SQLException {
pds.setInactiveConnectionTimeout(inactiveConnectionTime);
return this;
}
public OracleUcpDataSourceBuilder maxIdleTime(int maxIdleTime) throws SQLException {
pds.setMaxIdleTime(maxIdleTime);
return this;
}
public PoolDataSource build() {
return pds;
}
}
Preferably, I would like to be able to apply the properties directly to the builder in one place. is this possible? what changes would I have to make?
Thanks...
Here is your builder, sir
public class OracleUcpDataSourceBuilder {
private Map<String, String> properties = new HashMap<String, String>();
private static final String[] REQ_PROPERTIES = new String[] {"username", "password", "URL"};
public static OracleUcpDataSourceBuilder create() {
return new OracleUcpDataSourceBuilder();
}
public DataSource build() {
for (String prop : REQ_PROPERTIES) {
Assert.notNull(properties.get(prop), "Property is required:" + prop);
}
PoolDataSource result = PoolDataSourceFactory.getPoolDataSource();
bind(result);
return result;
}
private void bind(DataSource result) {
MutablePropertyValues properties = new MutablePropertyValues(this.properties);
new RelaxedDataBinder(result).bind(properties);
}
public OracleUcpDataSourceBuilder URL(String url) {
this.properties.put("URL", url);
return this;
}
public OracleUcpDataSourceBuilder username(String username) {
this.properties.put("username", username);
return this;
}
public OracleUcpDataSourceBuilder password(String password) {
this.properties.put("password", password);
return this;
}
}
Just define a bean like this:
#Bean (name = "readOnlyDataSource")
#ConfigurationProperties(prefix = "datasource.readonly")
public DataSource getReadOnlyDataSource() {
return OracleUcpDataSourceBuilder.create().build();
}
Just make sure that the property names are correct. Spring will take care of the rest.
Note: I use DataSourceBuilder or Spring as a reference.. You can check it's source code also.
Edit: Added some methods to make sure some properties are configured. But this way, you need to set those properties manually to make sure that they're available.