I have a #RestController with bunch of Rest endpoints as methods.
#RequestMapping(path="",method=RequestMethod.POST)
public void create(Principal principal) {
String userName = principal.getName();
User user = UsersRepository.loadUser(userName);
//....
}
#RequestMapping(path="/{groupId}",method=RequestMethod.DELETE)
public void deleteGroup(#PathVariable String groupId, Principal principal) {
String userName = principal.getName();
User user = UsersRepository.loadUser(userName);
//....
}
In each method I have to repeat this code:
String userName = principal.getName();
User user = UsersRepository.loadUser(userName);
Is there a way not to repeat this in each method and get it in the class, and consume it in each method ?
1) Very basic but why not simply extract it in a private method :
public User getUser(Principal principal){
String userName = principal.getName();
User user = UsersRepository.loadUser(userName);
//....
return user;
}
You could write so :
#RequestMapping(path="",method=RequestMethod.POST)
public void create(Principal principal) {
User user = getUser(principal);
//....
}
2) More advanced : you could use a Spring interceptor that reads in the request, loads the user and wires it in a bean with a request scope.
Related
I have a method where I require the argument name and I have it set as a session attribute as it will be fixed through out the session. However, I have trouble adding it to the function. Any help is appreciated.
LoginController class that sets the session attribute
#Controller
#SessionAttributes({"name", "date"})
public class LoginController {
#Autowired
LoginService service;
/*
* Map /login to this method
* localhost:8080/spring-mvc/login
* spring-mvc -> dispatcher (todo-servlet.xml)
* dispatcher detects login url
* dispatcher use view resolver to find .jsp file based on String. In this case, login
* view resolver locates login.jsp and shows the content to user via http
*/
#RequestMapping(value = "/test")
// Mark this method as an actual repsonse back to user
#ResponseBody
public String test() {
return "Hello, world!";
}
#RequestMapping(value ="/")
public String returnLogin() {
return "redirect:/loginPage";
}
// Only handles get request from the login page
#RequestMapping(value = "/login", method= RequestMethod.GET)
public String loginPage() {
// search through the view resolver for login.jsp
return "login";
}
// Only handles post request from the login page
#RequestMapping(value = "/login", method= RequestMethod.POST)
// #RequestParm to be used for user input
// Model is used to supply attributes to views
// ModelMap has the same functionality has model except it has an addition function where it allows a collection of attributes
public String handleLogin(#RequestParam String name, #RequestParam String password, ModelMap model) {
if (service.validateUser(name, password)) {
// Send name to .jsp
// use addAttrible( nameAtJSP, nameInRequestParam ) to check for null
model.addAttribute("name", name);
model.addAttribute("passWelcome", password);
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
String date = sdf.format(new Date());
model.addAttribute("date", date);
}
else {
model.put("errorMessage","Invalid credentials");
return loginPage();
}
return "welcome";
}
My controller class. I've commented the part that I need to add the session attribute.
#Controller
public class ToDoController {
#Autowired
private ToDoService service;
#RequestMapping(value = "/list-todo", method= RequestMethod.GET)
public String showToDo(ModelMap model, String name, String date) {
model.addAttribute("toDos", service.retrieveTodos("in28Minutes"));
model.addAttribute("name", name);
model.addAttribute("date", date);
// Only returns to the jsp
return "list-todos";
}
#RequestMapping(value = "/add-todo", method= RequestMethod.GET)
public String addToDo() {
return "addToDo";
}
#RequestMapping(value = "/add-todo", method= RequestMethod.POST)
public String addToDo(ModelMap model,#RequestParam String description) {
// SESSION ATTRIBUTE NAME
model.addAttribute("name");
service.addTodo((String) model.get("name"), description, new Date(), false);
model.clear();
return "redirect:/list-todo";
}
#SessionAttributes doesn't do what you are trying to achieve.
From javadoc of #SessionAttributes:
NOTE: Session attributes as indicated using this annotation correspond
to a specific handler's model attributes, getting transparently stored
in a conversational session. Those attributes will be removed once the
handler indicates completion of its conversational session. Therefore,
use this facility for such conversational attributes which are
supposed to be stored in the session temporarily during the course of
a specific handler's conversation.
For permanent session attributes, e.g. a user authentication object,
use the traditional session.setAttribute method instead.
So, what you need to do is:
public String handleLogin(#RequestParam String name,
#RequestParam String password,
ModelMap model,
HttpSession httpSession) {
// your code here
httpSession.setAttribute("name", name);
// your code here
}
And then you can retrieve this session attribute in your ToDoController as httpSession.getAttribute("name");
Hello so i have this method in JwtUtill
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractEmail(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
But how can i request UserDetails in controller?
#GetMapping("/validateToken")
public String validateToken(#RequestHeader(value="token") String token) {
if(jwtUtil.validateToken(token,???)) {
}
}
Angular side
public isTokenExpired(): Observable<string> {
const headers = new HttpHeaders().set('token', localStorage.getItem('token'));
return this.httpClient.get<string>('http://localhost:8080/api/validateToken', {headers, responseType: 'text' as 'json'});
}
Also as frontend im using angular
You can simply inject it using #AuthenticationPrincipal. Eg:
#GetMapping("/validateToken")
public String validateToken(#AuthenticationPrincipal UserDetails userDetails, ...
It seems like you are using jwt, you don't need UserDetails to compare it with.
change methods as :
public Boolean validateToken(String token) {
final String username = extractEmail(token);
return (!StringUtils.isEmpty(username) && !isTokenExpired(token));
}
#GetMapping("/validateToken")
public String validateToken(#RequestHeader(value="token") String token) {
if(jwtUtil.validateToken(token)) {
}
}
If your token is invalid you will not get exception in extractEmail method and if it is expired then method isTokenExpired will return false.
UserDetails comes in the security context in the principal
UserDetails userDetails =
(UserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
I have the following controller for logout I want to get the name of the logout user how can I acheive this ?
String userName=(String)session.getAttribute("name");
this line not working
Logout Controller
#RequestMapping(value = "/session", method = RequestMethod.DELETE)
public #ResponseBody ResponseEntity<?> logout(HttpSession session){
String userName=(String)session.getAttribute("name");
System.out.println("name: " + userName);
session.invalidate();
return ResponseEntity.ok("user logged out");
}
I do not have currently any Spring projects near me, but as I remember, it could be possible to do it like this:
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
As I said, I'm not sure if it's working. It's been a while since I last used Spring, but let me know if it is.
Try this.
#Component
public class LogoutListener implements
ApplicationListener<SessionDestroyedEvent> {
private static final Logger logger =
LoggerFactory.getLogger(LogoutListener.class);
#Override
public void onApplicationEvent(SessionDestroyedEvent event) {
List<SecurityContext> lstSecurityContext =
event.getSecurityContexts();
UserDetails ud;
for (SecurityContext securityContext : lstSecurityContext)
{
ud = (UserDetails)
securityContext.getAuthentication().getPrincipal();
logger.info("Logout|Session destroyed User: [{}]",
ud.getUsername());
}
}
}
I have an spring mvc web application in which users login to session "session.setAttribute" classically. Whenever I need loggedin user data I use this data.
Now I want to add android app and what I want to learn do I have to add additional methods for each android request and send user data within it?
Or Is there away to make a request to same methods.
What is the consept for this kind of cloud apps? Do I have to write different methods for android requests? Because it is not possible session.getAttribute when wemake an android request, it returns null.
User user = userService.getByUserNameAndPassword(userName, password);
if (user != null) {
if (user.isActive()) {
Account account = new Account(user, request.getRemoteAddr());
HttpSession httpSession = request.getSession(true);
AccountRegistry.add(httpSession);
httpSession.setAttribute(Constant.ACCOUNT, account);
result.put(Constant.REF, Constant.SUCCESS);
}
public class Account {
private UserRightsHandler userRightsService = null;
private User user;
private String ipAddress;
private boolean admin;
public Account(User user, String ipAddress) {
this.user = user;
this.ipAddress = ipAddress;
userRightsService = new UserRightsHandler(user);
setAdmin(userRightsService.isAdmin());
}
public UserRightsHandler getUserRightsService() {
return userRightsService;
}
public User getUser() {
return this.user;
}
public String getIpAddress() {
return ipAddress;
}
public boolean isAdmin() {
return admin;
}
private void setAdmin(boolean admin) {
this.admin = admin;
}
}
public class AccountRegistry {
private static final Map<String, HttpSession> sessions = new HashMap<String, HttpSession>();
public static void add(HttpSession session) {
sessions.put(session.getId(), session);
}
public static void remove(HttpSession session) {
if (session != null) {
sessions.remove(session.getId());
session.setAttribute(Constant.ACCOUNT, null);
session.invalidate();
}
}
public static HttpSession getByHttpSessionID(String httpSessionID) {
Set<String> keys = sessions.keySet();
Iterator it = keys.iterator();
while (it.hasNext()) {
String sID = (String) it.next();
HttpSession session = sessions.get(sID);
if (sID.equals(httpSessionID)) {
return session;
}
}
return null;
}
public static void removeByHttpSessionID(String httpSessionID) {
HttpSession session = getByHttpSessionID(httpSessionID);
remove(session);
}
public static Account getCurrentAccount() {
HttpServletRequest request = ContextFilter.getCurrentInstance().getRequest();
HttpSession session = request.getSession();
return (Account) session.getAttribute(Constant.ACCOUNT);
}
}
#RequestMapping(value = "/changeStatus", method = RequestMethod.POST)
public #ResponseBody
String changeStatus(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
User editor = AccountRegistry.getCurrentAccount().getUser();
}
You can ask user send their user and password at the start of Android app via custom authenticate request like /appLogin then if it is correct creditentals you can return a key to user (to app) and store it to some variable during app run. Then when user want to do something send a request to server you can send it to a function with mapping like /appExampleService then you can check at that function this key and device valid depending on how you handle custom login process then this function call existing function that is used for web browsers that have mapping /exampleService. For example;
#JsonSerialize
#RequestMapping("/appExampleService")
public int someServiceForAppClient(
#RequestParam(value = "key", required = true) String apikey,
#RequestParam(value = "param", required = true) String someParam{
String name=userDAO.getUsernameFromApiKey(apikey);
return someService(someParam, name);
}
#JsonSerialize
#RequestMapping("/exampleService")
public int someServiceForWebClient(
#RequestParam(value = "param", required = true) String someParam) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String name = auth.getName();
return someService(someParam, name);
}
public int someService(String someParam,String name){
return doBusiness(someParam, name);
}
userDAO is just something I created for to get info of user with given key. And there is a service for App login as well which return that key to user when he started the app send his username and pass
I am developing a web application that has the following requirements:
Allow the user to login
On the server side, the user is authenticated via a 3rd party REST web service.
The REST web service will return a unique token and key, if the authentication is successful.
Any subsequent requests to the REST web service must contain the token received in point 3 (above).
I am using spring-mvc and spring security for the web application.
So, I got a solution working, however I'm new to spring and not sure if the solution is correct.
Can someone please advise if:
Is the solution correctly implemented?
Does the solution impact performance in any way?
Does the solution create any security holes?
Thanks :)
Solution:
I created a MyUser object that will store the additional information received from the REST service.
public class MyUser implements Serializable {
private static final long serialVersionUID = 5047510412099091708L;
private String RestToken;
private String RestKey;
public String getRestToken() {
return RestToken;
}
public void setRestToken(String restToken) {
RestToken = restToken;
}
public String getRestKey() {
return RestKey;
}
public void setRestKey(String restKey) {
RestKey = restKey;
}
}
I then created a MyAuthenticationToken object that extends UsernamePasswordAuthenticationToken. This object will be used in the CustomAuthenticationProvider (point 3 below).
public class MyAuthenticationToken extends UsernamePasswordAuthenticationToken {
private static final long serialVersionUID = 7425814465946838862L;
private MyUser myUser;
public MyAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities, MyUser myUser){
super(principal, credentials, authorities);
this.myUser = myUser;
}
public MyUser getMyUser() {
return myUser;
}
public void setMyUser(MyUser myUser) {
this.myUser = myUser;
}
}
I created a custom authentication provider that will call the REST service for authentication and then store the additional information in the myUser and myAuthenticationToken objects.
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate (Authentication authentication) {
MyUser myUser = new MyUser();
MyAuthenticationToken authenticationToken = null;
String name = authentication.getName();
String password = authentication.getCredentials().toString();
//Just an example. This section will connect to a web service in order to authenticate the client
if (name.equals("justh") && password.equals("123456")) {
//Set the Token and Key Received from the REST WebService
myUser.setRestKey("RestKey");
myUser.setRestToken("RestToken");
List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
authenticationToken = new MyAuthenticationToken(name, password, grantedAuths, myUser);
return authenticationToken;
} else {
return null;
}
}
Finally, I can access the data stored in my controller
public ModelAndView adminPage(Authentication authentication) {
MyUser user = null;
//Get the additional data stored
if(authentication instanceof MyAuthenticationToken){
user = ((MyAuthenticationToken)authentication).getMyUser();
}
ModelAndView model = new ModelAndView();
model.addObject("title", "Spring Security Hello World");
model.addObject("message", "This is protected page - Admin Page!" + authentication.getName() + user.getRestKey() + user.getRestToken());
model.setViewName("admin");
return model;
}
Your approach is the right one. You should implement a custom AuthenticationManager and Authentication whenever your requirements exceeds a simple username password authentication flow.
But don't forget to comply with AuthenticationManager's interface contract.
I did something quite similar in my webmailer for authenticating against an smtp/imap server with javax.mail and it works flawlessly.