Signup by jwt in springboot webflux - spring-boot

I am a beginner on writing the webflux,but i am currectly writing a jwt authentication on springboot cloud apigateway.However, i found that i am just stuck on the signup part and reactive syntax due to the less resource on the internet.Here are my code:
signup service:
public Mono<String> signup(SignupDto user) {
return IsUsernameExist(user.getUsername()).
doOnNext(System.out::println)
.flatMap(isNameExist->{
if(isNameExist){
log.error("Username have already exist");
return Mono.empty();
}
Mono<user_info> savedusr = respository.save(new user_info(0, user.getUsername(),
passwordEncoder.encode(user.getPassword()),
user.getFullName(),
user.getEmail(),
user.getSkill_set(), user.getContact(), user.getCv(), user.getAddress_id()
, user.getAddress(), null, 3));
log.info("generating token");
//error
if (savedusr.equals(Mono.empty())){
log.info("error occur");
return Mono.error(new BadCredentialsException("error occur"));
}
return reactiveAuthenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
user.getUsername(),user.getPassword()
)
).map(this::generateToken
)
;
});
IsUsernameExist:
private Mono<Boolean> IsUsernameExist(String username){
return respository.findByyUsername(username).doOnNext(res-> System.out.print(" "))
.map(res->{
if (res==null){
return false;
}
log.error("repeated username");
return true;
}
);
log:
2023-02-03T03:54:33.793+08:00 DEBUG 98479 --- [or-http-epoll-3] o.s.d.r2dbc.core.NamedParameterExpander : Expanding SQL statement [SELECT * FROM user_info WHERE username = :username ;] to [SELECT * FROM user_info WHERE username = ? ;]
Only this log and empty respond are shown after it test the api on postman.It seems that it is because my code in signup service isnt been run(the log not appear) and i am just wondering why.Here are the reference code.I know there should many mistakes on my code,but please give my some advice.Any pointers for me is fine
generatetoken:
public String generateToken(Authentication user){
log.info("generate token");
Instant now=Instant.now();
String scope=user.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(" "));
String secret="AMDM:LM:LSMLdfsf";
Mono<Integer> MonoId =respository.findByyUsername(user.getName())
.map(user_info::getId);
JwtClaimsSet claims=JwtClaimsSet.builder()
.issuer("http://localhost:8080")
.issuedAt(now)
.expiresAt(now.plus(2, ChronoUnit.HOURS))
.subject(user.getName())
.claim("scope",scope)
.claim("secret",secret)
.build();
return this.jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
}
Securityfilterchain:
#Bean
public SecurityWebFilterChain filterChain(ServerHttpSecurity httpSecurity) throws Exception {
return httpSecurity
/*.csrf(csrf -> csrf.ignoringRequestMatchers("/Job/getRegionjobs/**",
"/Job/getalljobs","/login/oauth2/code/google"))*/
.csrf(csrf -> csrf.disable())
.authorizeExchange(auth->
auth.pathMatchers("/signup","/signin").permitAll()
.anyExchange().authenticated()
)
.oauth2ResourceServer(ServerHttpSecurity.OAuth2ResourceServerSpec::jwt)
.httpBasic(withDefaults())
.build();
ReactiveAuthManger :
#Override
public Mono<Authentication> authenticate(Authentication authentication) throws TypeMismatchException {
log.info("Received authentication request");
Mono<String> password=securityUserService.findByUsername(authentication.getName())
.doOnNext(System.out::println)
.map(UserDetails::getPassword);
return Mono.just(authentication)
.doOnNext(authentication1 -> System.out.println("start"))
.switchIfEmpty(Mono.error(new RuntimeException()))
.map(auth->{
System.out.println(passwordEncoder.encode(auth.getCredentials().toString()));
//if(Mono.just(passwordEncoder.matches(auth.getCredentials().toString(),password.doOnNext(System.out::println).subscribe()))){
password.map(
res -> {
log.info("STart match");
if(passwordEncoder.matches(auth.getCredentials().toString(), res)){
log.info("password match");
return new UsernamePasswordAuthenticationToken(
auth.getName(),null,auth.getAuthorities()
);
}else {
throw new BadCredentialsException("Bad Credients");
}
});
return auth;
});
}}
SecurityUserService:
#Service
#Slf4j
public class SecurityUserService implements ReactiveUserDetailsService {
private final UserRespository userRespository;
public SecurityUserService(UserRespository userRespository) {
this.userRespository = userRespository;
}
#Override
public Mono<UserDetails> findByUsername(String username) {
log.info("get user");
return userRespository.findByyUsername(username)
.doOnNext(System.out::println)
.switchIfEmpty(Mono.error(new RuntimeException()))
.map(
SecurityUser::new
);

The main problem is that the finding existing user function logic is wrong after i observe the debug log.
Here is my final version and it finally works:
private Mono<Boolean> IsUsernameExist(String username){
return respository.findByyUsername(username)
.doOnNext(res-> System.out.print(" afdasd"))
.doOnError(res->{log.info("good");
Mono.just(false);
})
.map(res->true
);
public Mono<String> signup(user_info user) {
log.info("signup start");
return Mono.just(user).doOnNext(System.out::println)
.switchIfEmpty(Mono.error(new RuntimeException()))
.flatMap(Monouser->{
Mono<Boolean> isuserexist=IsUsernameExist(Monouser.getUsername())
.doOnNext(System.out::println)
.switchIfEmpty(Mono.just(false)) //no user found => can register
.mapNotNull(res-> res
);
Monouser.setPassword( //encode the password
passwordEncoder.encode(user.getPassword()));
Mono<user_info> savedusr = respository.save(
Monouser).log();
return isuserexist.doOnNext(System.out::println) //check the username exist in my db
.switchIfEmpty(Mono.error(new RuntimeException()))
.flatMap(
res->{
log.info("start the map");
if(res){
log.info("error");
return null;
}
else {
savedusr.subscribe(); //execute the saving user
log.info("check");
return reactiveAuthenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
user.getUsername(),user.getPassword()
)
).map(this::generateToken)
;
}
});}
It seems that there are still have many flaws in my code.Any advice are welcome for me thank you

Related

Trust Anchor for Certification path not found - https POST Request with Volley not working

this question is asked very often, but all the answers 2 or 3 years old. I have a login for my Android app and the date is saved in a MySQL database on my cloud server which has a SSL certificate.
When I test my app on my local machine everything is fine, but when I try to connect with my cloud server I get the message "Trust Anchor for Certification path not found". My credentials are ok.
I know that I have to set a sslSocketFactory. I tried so many, but one worked. I´m sitting now for days. May someone had a idea how to solve
here my code without sslSocketFactory
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.logintest, container, false);
btn_login = view.findViewById(R.id.btn_logintest);
email = view.findViewById(R.id.etEmail);
password = view.findViewById(R.id.etPassword);
btn_login.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
login();
}
public void login() {
str_email = email.getText().toString();
str_password = password.getText().toString();
if(!str_email.equals("") && !str_password.equals("")) {
StringRequest request = new StringRequest(Request.Method.POST, URL_LOGIN, new Response.Listener<String>() {
#Override
public void onResponse(String response) {
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction= fragmentManager.beginTransaction();
DummyFragment dummyFragment = new DummyFragment();
fragmentTransaction.replace(R.id.container,dummyFragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
Log.e("Text: ", response);
Toast.makeText(getActivity(), "erfolgreicher Text: " +response, Toast.LENGTH_LONG).show();
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(getActivity(), "Text: " +error.getMessage().toString(), Toast.LENGTH_SHORT).show();
}
}
) {
#Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String, String> params = new HashMap<>();
params.put("str_email", email.getText().toString());
params.put("str_password", password.getText().toString());
return params;
}
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
try {
HashMap<String, String> headers = new HashMap<>();
String credentials = "xxxxxxxxx:xxxxxxxxxx";
String auth = "Basic "
+ Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);
headers.put("Content-Type", "application/json");
headers.put("Authorization", auth);
return headers;
} catch (Exception e) {
Log.e(TAG, "Authentication failure");
Toast.makeText(getActivity(), "" +e, Toast.LENGTH_LONG).show();
}
return super.getHeaders();
}
};
RequestQueue requestQueue = Volley.newRequestQueue(getActivity().getApplicationContext());
requestQueue.add(request);
}
else {
if (email.getText().toString().equals("")) {
Toast.makeText(getActivity(), "Bitte Email Adresse eingeben", Toast.LENGTH_SHORT).show();
} else if (password.getText().toString().equals("")) {
Toast.makeText(getActivity(), "Bitte Passwort eingeben", Toast.LENGTH_SHORT).show();
}
}
}
});
return view;
}
}
I tried the code from android developer page, but some error. I tried to set network_security.xml but it didn't worked for me. When I test my credentials with postman I get response 200.

Spring Webflux(Mono/Flux) with AOP triggering REST call at interception and working with Mono/Flux

I have written an #Aspect to intercept Reactive Methods that return values in Mono/Flux. Using #AfterReturning advice, i'm trying to fire an APNS notification by calling a webservice.
unfortunately the processNotification Mono services is immediately returning onComplete signal without executing the chain of calls. Below is my sample program.
#Aspect
#Component
#Slf4j
public class NotifyAspect{
private final NotificationServiceHelper notificationServiceHelper;
#Autowired
public NotifyAspect(NotificationServiceHelper notificationServiceHelper) {
this.notificationServiceHelper = notificationServiceHelper;
}
#AfterReturning(pointcut = "#annotation(com.cupid9.api.common.annotations.Notify)", returning = "returnValue")
public void generateNotification(JoinPoint joinPoint, Object returnValue) throws Throwable {
log.info("AfterReturning Advice - Intercepting Method : {}", joinPoint.getSignature().getName());
//Get Intercepted method details.
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//Get the Notification Details.
Notify myNotify = method.getAnnotation(Notify.class);
if (Mono.class.isAssignableFrom(returnValue.getClass())) {
Mono<Object> result = (Mono<Object>) returnValue;
result.doOnSubscribe(o -> {
log.debug("On Subscription...");
notificationServiceHelper.processNotification(myNotify.notificationType())
.doOnError(throwable -> {
log.error("Exception in notification processor",throwable);
});
});
}
}
}
#Slf4j
#Service
public class NotificationServiceHelper {
private ReactiveUserProfileRepository userProfileRepository;
#Value("${services.notification.url}")
private String notificationServiceUrl;
private RestWebClient restWebClient;
#Autowired
public NotificationServiceHelper(RestWebClient restWebClient,
ReactiveUserProfileRepository reactiveUserProfileRepository) {
this.restWebClient = restWebClient;
this.userProfileRepository = reactiveUserProfileRepository;
}
public Flux<Notification> processNotification(NotificationSchema.NotificationType notificationType) {
/*Get user profile details*/
return SessionHelper.getProfileId()
.switchIfEmpty( Mono.error(new BadRequest("Invalid Account 1!")))
.flatMap(profileId ->
Mono.zip(userProfileRepository.findByIdAndStatus(profileId, Status.Active), SessionHelper.getJwtToken()))
.switchIfEmpty( Mono.error(new BadRequest("Invalid Account 2!")))
.flatMapMany(tuple2 ->{
//Get user details and make sure there are some valid devices associated.
var userProfileSchema = tuple2.getT1();
log.info("Processing Notifications for User Profile : {}", userProfileSchema.getId());
if (Objects.isNull(userProfileSchema.getDevices()) || (userProfileSchema.getDevices().size() < 1)) {
return Flux.error(new InternalServerError("No Devices associate with this user. Can not send notifications."));
}
//Build Notification message from the Notification Type
var notificationsMap = new LinkedHashSet<Notification>();
userProfileSchema.getDevices().forEach(device -> {
var notificationPayload = Notification.builder()
.notificationType(notificationType)
.receiverDevice(device)
.receiverProfileRef(userProfileSchema.getId())
.build();
notificationsMap.add(notificationPayload);
});
//Get session token for authorization
var jwtToken = tuple2.getT2();
//Build the URI needed to make the rest call.
var uri = UriComponentsBuilder.fromUriString(notificationServiceUrl).build().toUri();
log.info("URI built String : {}", uri.toString());
//Build the Headers needed to make the rest call.
var headers = new HttpHeaders();
headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
headers.add(HttpHeaders.AUTHORIZATION, jwtToken);
var publishers = new ArrayList<Mono<ClientResponse>>();
notificationsMap.forEach(notification -> {
publishers.add(restWebClient.post(uri, headers, notification));
});
return Flux.merge(publishers).flatMap(clientResponse -> {
var httpStatus = clientResponse.statusCode();
log.info("NotificationService HTTP status code : {}", httpStatus.value());
if (httpStatus.is2xxSuccessful()) {
log.info("Successfully received response from Notification Service...");
return clientResponse.bodyToMono(Notification.class);
} else {
// return Flux.empty();
return clientResponse.bodyToMono(Error.class)
.flatMap(error -> {
log.error("Error calling Notification Service :{}", httpStatus.getReasonPhrase());
if (httpStatus.value() == 400) {
return Mono.error(new BadRequest(error.getMessage()));
}
return Mono.error(new InternalServerError(String.format("Error calling Notification Service : %s", error.getMessage())));
});
}
});
}).doOnError(throwable -> {
throw new InternalServerError(throwable.getMessage(), throwable);
});
}
}
How can we trigger this call in async without making the interception wait.. right now processNotification is always returning onComplete signal without executing. The chain is not executing as expected
#Target({ElementType.PARAMETER, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Log {
public String title() default "";
}
#SuppressWarnings({"unchecked"})
#Around("#annotation(operlog)")
public Mono<Result> doAround(ProceedingJoinPoint joinPoint, Log operlog) {
Mono<Result> mono;
try {
mono = (Mono<Result>) joinPoint.proceed();
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
return mono.doOnNext(result -> {
//doSomething(result);
};
}

How to send a HTTP response in Zuul PRE_TYPE Filter

I want to prevent not logged user form accessing the proxy. I can throw an exception but the response is 404 instead of `401 or '403'. It it possible?
Filter code:
#Component
public class CustomZuulFilter extends ZuulFilter {
//FIXME - if 401,403 get the new token??, fallbackMethod = "fall",
#HystrixCommand(
commandProperties = {
#HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000"),
#HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")
}
)
#Override
public Object run() {
logger.debug("Adding zulu header");
String userName = getLoggedUser();
RequestContext ctx = RequestContext.getCurrentContext();
if (userName == null) {
// throw new RuntimeException("User not authenticated");
logger.info("User not authenticated");
ctx.setResponseStatusCode(401);
ctx.sendZuulResponse();
return null;
}
return null;
}
private String getLoggedUser() {
[...]
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public String filterType() {
return PRE_TYPE;
}
#Override
public int filterOrder() {
return PRE_DECORATION_FILTER_ORDER - 1;
}
}
It might be a bit late, but i think you can remove ctx.sendZuulResponse();
and add ctx.setSendZuulResponse(false);

How to return http response in Spring

I want to replace the http exception with a response, that is, I use responseentity I want to return for example 409 if the user is not found by name, and 509 if not found by mail, can I determine the error number and its description in responseEntity? If so, can give an example?
#RequestMapping(value = "/change", method = RequestMethod.POST)
public void change(#RequestBody User user) throws Exception {
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
User userByUsername = userService.findUserByUsername(userDetails.getUsername());
if (userByUsername == null) {
throw new Exception("Пользователь не найден");
//TODO: return ResponseEntity code 409 if userByusername not found
}
user.setId(userByUsername.getId());
if (user.getPassword() == null) {
user.setPassword(userByUsername.getPassword());
}
user.setRoles(userByUsername.getRoles());
userService.save(user);
}
public ResponseEntity<Object> change(#RequestBody User user) throws Exception {
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.CONFLICT, "not found user by username",409);
User userByUsername = userService.findUserByUsername(userDetails.getUsername());
if (userByUsername == null) {
return new ResponseEntity<>(errorResponse, new HttpHeaders(), errorResponse.getStatus());
}
user.setId(userByUsername.getId());
if (user.getPassword() == null) {
user.setPassword(userByUsername.getPassword());
}
user.setRoles(userByUsername.getRoles());
userService.save(user);
return null;
}
public class ErrorResponse {
private HttpStatus status;
private String message;
private Integer errors;
public ErrorResponse(HttpStatus status, String message, Integer errors) {
super();
this.status = status;
this.message = message;
this.errors = errors;
}
public HttpStatus getStatus() {
return status;
}
}

Keycloak.json configuration file import

I have configured my goclient with keycloak.I am reading the keycloak server details from keycloak.json file which is present inside my Go client.
My whole setup is in Kubernetes cluster.Everytime when new keycloak server comes up how should I pass keycloak.json file to the Go client so that it can read the details from json file?
Is there a way I can get the keycloak.json file with all the client configuration details without having to login to keycloak and download it ?
"realm": "SolutionAdmin",
"auth-server-url": "localhost:8081/auth",
"ssl-required": "none",
"resource": "admin",
"credentials": {
"secret": "6ee0f523-c392-4406-bb14-ba315125c6ea"
},
"confidential-port": 0
}
if you want to have a pre-configured embedded keycloak server, you'll need to extend KeycloakApplication and have it configured to trigger in lieu of the base class. in this example, KeycloakProperties is just a representation of all the keycloak properties we used in application.properties. but you can get the gist of it. fair warning: i did not write this, but am figuring out how a coworker did for another project.
public class EmbeddedKeycloakApplication extends KeycloakApplication {
public final KeycloakProperties keycloakProperties;
public EmbeddedKeycloakApplication() {
super();
keycloakProperties = SpringContextAdapter.getBean(KeycloakProperties.class);
System.getProperties().putAll(keycloakProperties.getRealmProperties());
createMasterRealmAdminUser();
createRealm();
}
private void createMasterRealmAdminUser() {
log.debug("Creating administrative user.");
String username = keycloakProperties.getServer(KeycloakProperties.ADMIN_USER_USERNAME);
String password = keycloakProperties.getServer(KeycloakProperties.ADMIN_USER_PASSWORD);
String email = keycloakProperties.getServer(KeycloakProperties.ADMIN_USER_EMAIL);
KeycloakSession session = getSessionFactory().create();
ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session);
try {
session.getTransactionManager().begin();
if (!applianceBootstrap.isNoMasterUser()) {
log.debug("Administrative user already exists. No work to do.");
return;
}
applianceBootstrap.createMasterRealmUser(username, password);
RealmModel adminRealm = session.realms().getRealm(Config.getAdminRealm());
UserModel adminUser = session.users().getUserByUsername(username, adminRealm);
adminUser.setEmail(email);
adminUser.setEmailVerified(true);
session.getTransactionManager().commit();
log.info("Created administrative user {}", username);
} catch (Exception ex) {
log.error("Couldn't create keycloak master admin user: {}", ex.getMessage());
session.getTransactionManager().rollback();
}
session.close();
}
private void createRealm() {
String realmImportFilename = keycloakProperties.getRealmImportFile();
KeycloakSession session = getSessionFactory().create();
String realmId = keycloakProperties.getRealm(KeycloakProperties.REALM_ID);
try {
session.getTransactionManager().begin();
RealmManager manager = new RealmManager(session);
if (manager.getRealm(realmId) != null) {
log.debug("{} realm already exists. No work to do.", realmId);
return;
}
Resource realmImportFile = new ClassPathResource(realmImportFilename);
RealmRepresentation rep =
JsonSerialization.readValue(
realmImportFile.getInputStream(), RealmRepresentation.class, true);
manager.importRealm(rep);
log.info("Imported Realm json file {}", realmImportFilename);
session.getTransactionManager().commit();
} catch (Exception e) {
log.error("Failed to import Realm json file {}: {}", realmImportFilename, e.getMessage(), e);
session.getTransactionManager().rollback();
}
session.close();
}
}
#Configuration
public class EmbeddedKeycloakConfig {
#Bean
ServletRegistrationBean<HttpServlet30Dispatcher> keycloakJaxRsApplication(
KeycloakProperties keycloakProperties, DataSource dataSource) throws NamingException {
mockJndiEnvironment(dataSource);
var contextPath = keycloakProperties.getServer(KeycloakProperties.SERVER_CONTEXT_PATH);
ServletRegistrationBean<HttpServlet30Dispatcher> servlet =
new ServletRegistrationBean<>(new HttpServlet30Dispatcher());
servlet.addInitParameter(
"javax.ws.rs.Application", EmbeddedKeycloakApplication.class.getName());
servlet.addInitParameter(
ResteasyContextParameters.RESTEASY_SERVLET_MAPPING_PREFIX, contextPath);
servlet.addInitParameter(ResteasyContextParameters.RESTEASY_USE_CONTAINER_FORM_PARAMS, "true");
servlet.addInitParameter(ResteasyContextParameters.RESTEASY_DISABLE_HTML_SANITIZER, "true");
servlet.addUrlMappings(contextPath + "/*");
servlet.setLoadOnStartup(1);
servlet.setAsyncSupported(true);
return servlet;
}
#Bean
FilterRegistrationBean<EmbeddedKeycloakRequestFilter> keycloakSessionManagement(
KeycloakProperties keycloakProperties) {
FilterRegistrationBean<EmbeddedKeycloakRequestFilter> filter = new FilterRegistrationBean<>();
filter.setName("Keycloak Session Management");
filter.setFilter(new EmbeddedKeycloakRequestFilter());
filter.addUrlPatterns(
keycloakProperties.getServer(KeycloakProperties.SERVER_CONTEXT_PATH) + "/*");
return filter;
}
private void mockJndiEnvironment(DataSource dataSource) throws NamingException {
NamingManager.setInitialContextFactoryBuilder(
env ->
environment ->
new InitialContext() {
#Override
public Object lookup(Name name) {
return lookup(name.toString());
}
#Override
public Object lookup(String name) {
if ("spring/datasource".equals(name)) {
return dataSource;
}
return null;
}
#Override
public NameParser getNameParser(String name) {
return CompositeName::new;
}
#Override
public void close() {
// NOOP
}
});
}
}

Resources