Spring Boot + Shiro + Thymleaf - css

I have some problems with my webapp. Here is my code:
Config:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"app.controllers", "app.service"})
public class MainStConfig extends WebMvcConfigurerAdapter {
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/",
"classpath:/webjars/",
"classpath:/tempplates/"
};
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations(CLASSPATH_RESOURCE_LOCATIONS);
}
#Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter() {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setLoginUrl("/login.html");
shiroFilter.setSuccessUrl("/index.html");
shiroFilter.setUnauthorizedUrl("/index.html?error");
Map<String, String> filterChain = new HashMap<>();
filterChain.put("/", "anon");
filterChain.put("/login", "authcBasic");
filterChain.put("/logout", "logout");
filterChain.put("/admin/**", "authc,roles[ADMIN]");
filterChain.put("/student/**", "authc,roles[STUDENT]");
filterChain.put("/teacher/**", "authc,roles[TEACHER]");
//filterChain.put("/student/**", "authc,roles[STUDENT]");
//filterChain.put("/teacher/**", "roles,roles[TEACHER]");
shiroFilter.setFilterChainDefinitionMap(filterChain);
shiroFilter.setSecurityManager(securityManager());
Map<String, Filter> filters = new HashMap<>();
filters.put("anon", new AnonymousFilter());
filters.put("authc", new FormAuthenticationFilter());
filters.put("logout", new LogoutFilter());
filters.put("roles", new RolesAuthorizationFilter());
filters.put("user", new UserFilter());
shiroFilter.setFilters(filters);
return shiroFilter;
}
#Bean
public org.apache.shiro.mgt.SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());
return securityManager;
}
#Bean(name = "userRealm")
#DependsOn("lifecycleBeanPostProcessor")
public UserRealm userRealm() {
return new UserRealm();
}
#Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
IndexController:
#Controller
#RequestMapping("/")
public class IndexController {
#RequestMapping(value = "/", method = RequestMethod.GET)
String start() {
return "index";
}
#RequestMapping(value = "/index", method = RequestMethod.GET)
String index() {
return "index";
}
#RequestMapping("/login")
String login() {
return "login";
}
}
LoginController
#Controller
public class LoginController {
private Session session;
#ModelAttribute("userR")
public User getUser() {
return new User();
}
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String login() {
return "login";
}
#RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logoutt() {
return "redirect:/index";
}
#RequestMapping(value = "/admin/index", method = RequestMethod.GET)
public String admin() {
return "admin/index";
}
#RequestMapping(value = "/student/index", method = RequestMethod.GET)
public String student() {
return "student/index";
}
#RequestMapping(value = "/teacher/index", method = RequestMethod.GET)
public String teacher() {
return "teacher/index";
}
#RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(Model model, #ModelAttribute("userR") User user, RedirectAttributes redirectAttrs, SessionStatus status) {
Subject currentUser = SecurityUtils.getSubject();
model.addAttribute("login", user.getLogin());
if (StringUtils.hasText(user.getLogin()) && StringUtils.hasText(user.getPassword())) {
try {
UsernamePasswordToken token = new UsernamePasswordToken(user.getLogin(), user.getPassword());
token.setRememberMe(true);
currentUser.login(token);
session = currentUser.getSession(false);
if(currentUser.hasRole("ADMIN")) {
status.setComplete();
return "redirect:/admin/index";
}
if(currentUser.hasRole("STUDENT")) {
status.setComplete();
return "redirect:/student/index";
}
if(currentUser.hasRole("TEACHER")) {
status.setComplete();
return "redirect:/teacher/index";
}
} catch (Exception e) {
return "login";
}
return "redirect:index";
} else {
return "login";
}
}
#RequestMapping(value = "/logout", method = RequestMethod.POST)/*#RequestMapping(value = "/logout", method = RequestMethod.POST)*/
public String logout() {
Subject currentUser = SecurityUtils.getSubject();
try {
session.stop();
currentUser.logout();
return "redirect:/index";
} catch (Exception e) {
return "redirect:/index";
}
}
}
So, with this code always when I'm starting index page in my console I can see pages without layout with css - white pages without bootstrap but when i change this method i LoginController:
#RequestMapping(method = RequestMethod.POST)
public String logout() {
Subject currentUser = SecurityUtils.getSubject();
try {
session.stop();
currentUser.logout();
return "redirect:/index";
} catch (Exception e) {
return "redirect:/index";
}
}
to:
#RequestMapping(value = "/logout", method = RequestMethod.POST)
then everything works, I can see all colors etc but here is problem now, When I try to log out, for example from /admin/index then I'm redirecting to /admin/logout with Whitable error. Should be redirecting to index page like without "value = "/logout"" because then it works.
I use fot log out button:
<form th:action="#{logout}" method="POST">
<input type="submit" class="btn btn-info text-center center-block"
value="Wyloguj" />
</form>
And when I'm not using this "value = "logout"" then after log out in console I can see this warning:
2017-01-06 19:40:56.135 WARN 3400 --- [nio-8080-exec-6] o.s.web.servlet.PageNotFound : Request method 'GET' not supported
What am I doing wrong?

Related

How can I return a response in ASP.NET Core MVC middleware using MVC's content negotiation?

I have some ASP.NET Core MVC middleware to catch unhandled exceptions that I would like to return a response from.
While it is easy to just httpContext.Response.WriteAsync to write a string and e.g. use JsonSerializer to serialise an object to a string, I would like to use the standard serialisation settings and content negotiation so that if I change my default output formatting to XML or a text/xml accept header is sent when I have multiple output formatters configured then XML is returned, as it does if I return an ObjectResult from a controller.
Does anyone know how this can be achieved in middleware?
Here is my code so far which only writes JSON:
public class UnhandledExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly IOutputFormatter _outputFormatter;
private readonly IHttpResponseStreamWriterFactory _streamWriterFactory;
public UnhandledExceptionMiddleware(RequestDelegate next, JsonOutputFormatter outputFormatter, IHttpResponseStreamWriterFactory streamWriterFactory)
{
_next = next;
_outputFormatter = outputFormatter;
_streamWriterFactory = streamWriterFactory;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var error = new ErrorResultModel("Internal Server Error", exception.Message, exception.StackTrace);
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
await _outputFormatter.WriteAsync(new OutputFormatterWriteContext(context, _streamWriterFactory.CreateWriter, typeof(ErrorResultModel), error));
}
}
where ErrorResultModel is defined as:
public class ErrorResultModel
{
public string ResultMessage { get; };
public string ExceptionMessage { get; };
public string ExceptionStackTrace { get; };
public ErrorResultModel(string resultMessage, string exceptionMessage, string exceptionStackTrace)
{
ResultMessage = resultMessage;
ExceptionMessage = exceptionMessage;
ExceptionStackTrace = exceptionStackTrace;
}
}
This is not possible in ASP.NET Core 2.0 MVC.
This will be possible in 2.1:
public static class HttpContextExtensions
{
private static readonly RouteData EmptyRouteData = new RouteData();
private static readonly ActionDescriptor EmptyActionDescriptor = new ActionDescriptor();
public static Task WriteResultAsync<TResult>(this HttpContext context, TResult result)
where TResult : IActionResult
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var executor = context.RequestServices.GetService<IActionResultExecutor<TResult>>();
if (executor == null)
{
throw new InvalidOperationException($"No result executor for '{typeof(TResult).FullName}' has been registered.");
}
var routeData = context.GetRouteData() ?? EmptyRouteData;
var actionContext = new ActionContext(context, routeData, EmptyActionDescriptor);
return executor.ExecuteAsync(actionContext, result);
}
}
public class Program : StartupBase
{
public static Task Main(string[] args)
{
return BuildWebHost(args).RunAsync();
}
public static IWebHost BuildWebHost(string[] args)
{
return new WebHostBuilder().UseStartup<Program>().UseKestrel().Build();
}
public override void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore().AddJsonFormatters();
}
public override void Configure(IApplicationBuilder app)
{
app.Use((ctx, next) =>
{
var model = new Person("Krisian", "Hellang");
var result = new ObjectResult(model);
return ctx.WriteResultAsync(result);
});
}
}
public class Person
{
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public string FirstName { get; }
public string LastName { get; }
}

Handle org.thymeleaf.exceptions.TemplateInputException

I have the following controller logic. However, if I navigate to a non-existing page (e.g. /random-page), I end up with a TemplateInputException. How can I catch this and go to the 404 page?
#RequestMapping(value = { "{path:(?!resources|error).*$}", "{path:(?!resources|error).*$}/**" }, headers = "Accept=text/html")
public String index(final HttpServletRequest request) {
try {
String path = (String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
return path.split("/")[1];
} catch (Exception e) {
log.error("Failed to render the page. {}",e);
return "error/general";
}
}
Thymeleaf seems to be ignoring the ExceptionHandler:
#ExceptionHandler(Exception.class)
public ModelAndView handleAllException(Exception ex) {
ModelAndView model = new ModelAndView("error/generic_error");
model.addObject("errMsg", "this is Exception.class");
return model;
}
My workaround this problem for spring-boot (exception is view with message param):
#Controller
public class ErrorController implements org.springframework.boot.autoconfigure.web.ErrorController {
private static final String ERROR_PATH = "/error";
#Autowired
private ErrorAttributes errorAttributes;
#Override
public String getErrorPath() {
return ERROR_PATH;
}
#RequestMapping(ERROR_PATH)
public String error(HttpServletRequest request, Model model) {
Map<String, Object> errorMap = errorAttributes.getErrorAttributes(new ServletRequestAttributes(request), false);
String exception = (String) errorMap.get("exception");
if (exception != null && exception.contains("TemplateInputException")) {
errorMap.put("message", "Неверный запрос");
}
model.addAllAttributes(errorMap);
return "exception";
}
}

Why I can't Receive String and It's null

#RequestMapping(value = "/save",method = RequestMethod.POST)
#ResponseStatus(value= HttpStatus.OK)
public void save(String str) throws IOException {
System.out.println(str);
}
all I got is null:
You need to tell Spring where to get str from.
If you're sending the JSON
{ "str": "sasfasfafa" }
You'll need a class that deserialises from this and annotate the method parameter with #RequestBody.
public class StrEntity {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
}
public class MyController {
#RequestMapping(value = "/save",method = RequestMethod.POST)
#ResponseStatus(value= HttpStatus.OK)
public void save(#RequestBody StrEntity entity) throws IOException {
System.out.println(entity.getStr());
}
}
If you just want to send a string as the request body (i.e. sasfasfafa) instead of the JSON document you can do this:
public class MyController {
#RequestMapping(value = "/save",method = RequestMethod.POST)
#ResponseStatus(value= HttpStatus.OK)
public void save(#RequestBody String str) throws IOException {
System.out.println(str);
}
}
There is no way to send the JSON { "str": "sasfasfafa" } as request body and only have a String as a method parameter in the controller.
Use #RequestParam annotation to get the parameter.
#RequestMapping(value = "/save",method = RequestMethod.POST)
#ResponseStatus(value= HttpStatus.OK)
public void save(#RequestParam(name="str") String str) throws IOException {
System.out.println(str);
}

Spring MVC success, but page isn't refreshed, returns blank page

When I loaded at first run project, thanhviens.jsp will be loaded. But I add a new user, it return to thanhviens.jsp and doesn't have any data to display. It's a blank page. When I go back to index and click to /listUser, the page loads with the data I saved before that.
This is my code for the controller:
#Controller
#RequestMapping(value = "/account")
public class ThanhVienController {
#RequestMapping(value = "/ListUser", method = RequestMethod.GET)
public String getAllUser(ModelMap mm)
{
try {
List<Thanhvien> lst = ThanhVienDAO.layDS();
mm.addAttribute("listUser", lst);
} catch (Exception e) {
e.printStackTrace();
}
return "thanhviens";
}
#RequestMapping(value = "/adduser",method = RequestMethod.GET)
public String addUser(ModelMap mm)
{
mm.put("u", new Thanhvien());
return "add";
}
#RequestMapping(value = "/adduser", method = RequestMethod.POST)
public String addUser(#ModelAttribute(value = "u")Thanhvien user, ModelMap mm)
{
Integer check = null;
check = ThanhVienDAO.addUser(user);
if (check!=null) {
return "thanhviens";
}
else
{
mm.put("errMess", "Error!!");
return "add";
}
}
}
What's causing this and how can I fix it?
Because when you add user you are not setting the listUser model attribute. That's why it is not showing anything. below code should work.
#RequestMapping(value = "/adduser", method = RequestMethod.POST)
public String addUser(#ModelAttribute(value = "u")Thanhvien user, ModelMap mm)
{
Integer check = null;
check = ThanhVienDAO.addUser(user);
if (check!=null) {
List<Thanhvien> lst = ThanhVienDAO.layDS();
lst.add(user);//new user
mm.addAttribute("listUser", lst);
return "thanhviens";
}
else
{
mm.put("errMess", "Error!!");
return "add";
}
}

JAAS how to tell Glassfish which LoginModule to use?

I want to use JAAS Authentification for my webapp.
For that i have the following classes:
UserPrincipal:
import java.security.Principal;
public class UserPrincipal implements Principal {
private String name = "";
public UserPrincipal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
RolePrincipal:
import java.security.Principal;
public class RolePrincipal implements Principal {
private String name = "";
public RolePrincipal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
LoginModule:
public class MyLoginModule implements LoginModule {
private CallbackHandler callbackHandler = null;
private Subject subject = null;
private UserPrincipal userPrincipal = null;
private RolePrincipal rolePrincipal = null;
private String login = null;
private List<String> userGroups = null;
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
this.callbackHandler = callbackHandler;
this.subject = subject;
}
public boolean login() throws LoginException {
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("login");
callbacks[1] = new PasswordCallback("password", true);
try {
callbackHandler.handle(callbacks);
String name = ((NameCallback)callbacks[0]).getName();
String password = String.valueOf(((PasswordCallback) callbacks[1]).getPassword());
if(name != null && name.equals("admin") && password != null && password.equals("admin")) {
this.login = name;
this.userGroups = new ArrayList<String>();
this.userGroups.add("admin");
return true;
}
throw new LoginException("Authentication failed");
} catch (IOException e) {
throw new LoginException(e.getMessage());
} catch (UnsupportedCallbackException e) {
throw new LoginException(e.getMessage());
}
}
public boolean commit() throws LoginException {
this.userPrincipal = new UserPrincipal(this.login);
this.subject.getPrincipals().add(this.userPrincipal);
if(this.userGroups != null && this.userGroups.size() > 0) {
for(String groupName: this.userGroups) {
this.rolePrincipal = new RolePrincipal(groupName);
this.subject.getPrincipals().add(this.rolePrincipal);
}
}
return true;
}
public boolean abort() throws LoginException {
return false;
}
public boolean logout() throws LoginException {
this.subject.getPrincipals().remove(this.userPrincipal);
this.subject.getPrincipals().remove(this.rolePrincipal);
return true;
}
}
How do i have to tell my Glassfish server that he has to use MyLoginModule
as the LoginModule?
My web.xml security configuration is that:
<security-constraint>
<web-resource-collection>
<web-resource-name>Admin</web-resource-name>
<url-pattern>/admin/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<security-role>
<role-name>admin</role-name>
</security-role>
<login-config>
<auth-method>FORM</auth-method>
<realm-name>Admin</realm-name>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/error.jsp</form-error-page>
</form-login-config>
</login-config>
The Documentation i found is not really clear in what actually to do.
Hope someone knows!
Edit your config/login.conf and add your LoginModule for the realm you use. In your web.xml, you use the "Admin" realm (realm-name). So I guess your login.conf file should look like :
Admin {
com.mycompany.MyLoginModule required;
}

Resources