I'm having a rough time figuring out how to redirect to a page defined in tiles configuration.
Using Spring Security 4 with annotations and Tiles 3.
The CustomSuccessHandler below works but it doesn't resolve the targetUrl to the page defined in tiles configuration.
#Component
public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler{
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
protected void handle(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws IOException {
String targetUrl = determineTargetUrl(authentication);
if (response.isCommitted()) {
System.out.println("Can't redirect");
return;
}
test();
redirectStrategy.sendRedirect(request, response, targetUrl);
}
static void test() {
}
protected String determineTargetUrl(Authentication authentication) {
String url="";
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
List<String> roles = new ArrayList<String>();
for (GrantedAuthority a : authorities) {
roles.add(a.getAuthority());
}
if (isAdmin(roles)) {
url = "/admin";
} else if (isUser(roles)) {
url = "/user";
} else {
url="accessDenied";
}
return url;
}
I figured out that my problem was self-inflicted, as usual. I had neglected to define "admin"or "user" above, in my views.xml (tiles configuration) file. Once I configured the pages in views.xml, it started working as expected. Thanks!
Related
I want to gain access to the URLs set by the FormLoginConfigurer, in particular I want to extraxt the URL Strings for loginPage, loginProcessingUrl and failureUrl. These values are configured as follows:
public class WebSecurityConfig {
#Bean
public SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests(auth -> auth
.mvcMatchers("/").permitAll()
.mvcMatchers("/**").authenticated())
.formLogin(login -> login
.loginPage("/login")
.loginProcessingUrl("/authenticate")
.failureUrl("/login?error")
.successHandler(new CustomAuthenticationSuccessHandler())
.permitAll())
.build();
}
}
Within a CustomAuthenticationSuccessHandler, concrete inside the determineTargetUrl method, I now want to make some decisions based on these URLs. The SuccessHandler looks like this:
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException {
handleRedirect(request, response, authentication);
clearAuthenticationAttributes(request);
}
private void handleRedirect(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException {
String targetUrl = determineTargetUrl(request, authentication);
if (response.isCommitted()) return;
redirectStrategy.sendRedirect(request, response, targetUrl);
}
private String determineTargetUrl(HttpServletRequest request, Authentication authentication) {
Set<String> authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());
SavedRequest savedRequest = (SavedRequest) request.getSession()
.getAttribute("SPRING_SECURITY_SAVED_REQUEST");
if (authorities.contains("ROLE_ADMIN")) return "/admin";
if (authorities.contains("ROLE_USER")) return savedRequest.getRedirectUrl();
throw new IllegalStateException();
}
private void clearAuthenticationAttributes(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) return;
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
}
How can I extract the values for loginPage, loginProcessingUrl and failureUrl?
You should define those URLs somewhere that you can use as a reference in both places. It could be in your application.yml file:
security:
form:
login-url: "/login"
login-success-url: "/success"
other-property: 123
And in your code you inject them (note that the class has to be a bean):
#Configuration
public class WebSecurityConfig {
#Value("security.form.login-url")
private String loginUrl;
#Value("security.form.login-success-url")
private String loginSuccessUrl;
// ...
}
You can also create a ConfigurationProperties to improve the code https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.typesafe-configuration-properties
I'm trying to implement Abstract Auditable Entity in my current Microservice architecture. It is working fine for a single module but I'm confused on how to pass the SecurityContext across multiple modules.
I've already tried by transferring the token as a header from my zuul-service (auth-server) to other core modules and the value is always null.
Also, I tried passing the SecurityContext using feign client but it didn't work for me either.
Cannot get JWT Token from Zuul Header in Spring Boot Microservice Module
Audit Logging in Spring Microservices
Session Management in microservices
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
private final JwtConfig jwtConfig;
public JwtTokenAuthenticationFilter(JwtConfig jwtConfig) {
this.jwtConfig = jwtConfig;
}
private static final int FILTER_ORDER = 0;
private static final boolean SHOULD_FILTER = true;
private static final Logger logger = LoggerFactory.getLogger(AuthenticationFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest request1, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String header = request1.getHeader(jwtConfig.getHeader());
if (header == null || !header.startsWith(jwtConfig.getPrefix())) {
chain.doFilter(request1, response);
return;
}
/* new token getting code*/
String token = header.replace(jwtConfig.getPrefix(), "");
try {
Claims claims = Jwts.parser()
.setSigningKey(jwtConfig.getSecret().getBytes())
.parseClaimsJws(token)
.getBody();
String username = claims.getSubject();
System.out.println(username);
if (username != null) {
#SuppressWarnings("unchecked")
List<String> authorities = (List<String>) claims.get("authorities");
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(
username,
null, authorities.stream().map(
SimpleGrantedAuthority::new
).collect(Collectors.toList()));
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (Exception e) {
SecurityContextHolder.clearContext();
}
System.out.println(String.format("%s request to %s", request1.getMethod(), request1.getRequestURL().toString()));
/* return null;*/
request1.setAttribute("header",token);
chain.doFilter(request1, response);
}
}
I have a question regarding security implementation on my server. I am making a SpringBoot application which has a control panel like website on it, where 1 single admin inputs needed data and i have managed to secure that part fine like this :
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/*").authorizeRequests().anyRequest().hasRole("ADMIN")
.and().formLogin().loginPage("/login.jsp")
.failureUrl("/login.jsp?error=1").loginProcessingUrl("/login")
.permitAll().and().logout()
.logoutSuccessUrl("/login.jsp");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Create a default account
auth.inMemoryAuthentication()
.withUser("admin")
.password("admin")
.roles("ADMIN");
}
Every website url is on /*, and that works fine. The next thing i need to do is to retrieve data from my mobile app and it needs to be secure. urls that the app should use is /rest/**. I have a Student class that stores email(username) and password that is created by that admin on web site. As far as i've read i need token implementation.
How can I implement token authentication?
To implement token based authentication for a mobile app, with Spring Boot and Spring Security.
Create a TokenAuthenticationFilter
public class TokenAuthenticationFilter extends GenericFilterBean {
private AuthenticationManager authenticationManager;
public TokenAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String apiKey = httpRequest.getHeader("API-Key");
String token = httpRequest.getHeader("Access-Token");
try {
if (!StringUtils.isEmpty(apiKey)) {
processTokenAuthentication(apiKey);
}
chain.doFilter(request, response);
} catch (InternalAuthenticationServiceException internalAuthenticationServiceException)
{
SecurityContextHolder.clearContext();
logger.error("Internal authentication service exception", internalAuthenticationServiceException);
httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
catch(AuthenticationException authenticationException)
{
SecurityContextHolder.clearContext();
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage());
}
}
private void processTokenAuthentication(String apiKey) {
SessionCredentials authCredentials = new SessionCredentials(apiKey);
Authentication requestAuthentication = new PreAuthenticatedAuthenticationToken(authCredentials, authCredentials);
Authentication resultOfAuthentication = tryToAuthenticate(requestAuthentication);
SecurityContextHolder.getContext().setAuthentication(resultOfAuthentication);
}
private Authentication tryToAuthenticate(Authentication requestAuthentication) {
Authentication responseAuthentication = authenticationManager.authenticate(requestAuthentication);
if (responseAuthentication == null || !responseAuthentication.isAuthenticated()) {
throw new InternalAuthenticationServiceException("Unable to authenticate Domain User for provided credentials");
}
return responseAuthentication;
}
}
public class TokenAuthenticationProvider implements AuthenticationProvider {
private String apiKey;
public TokenAuthenticationProvider(String apiKey) {
this.apiKey = apiKey;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
SessionCredentials credentials = (SessionCredentials) authentication.getCredentials();
if (credentials != null && credentials.apiKey.equals(this.apiKey)) {
//Also evaluate the token here
Authentication newAuthentication = new PreAuthenticatedAuthenticationToken(apiKey, credentials);
newAuthentication.setAuthenticated(true);
return newAuthentication;
}
throw new BadCredentialsException("Bad credentials given.");
}
#Override
public boolean supports(Class<?> aClass) {
return aClass.equals(PreAuthenticatedAuthenticationToken.class);
}
}
Create Session Credentials Holder
public class SessionCredentials {
String apiKey;
String accessToken;
public SessionCredentials(String apiKey, String accessToken) {
this.apiKey = apiKey;
this.accessToken = accessToken;
}
public String getApiKey() {
return apiKey;
}
public String getAccessToken() {
return accessToken;
}
}
Finally Register These in your Security Config
//Leave whatever you had here
#Override
public void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new TokenAuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class);
String contentPathDir = String.format("/%s/**", contentPath);
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers("/authorization/**", "/public/**", "/management/**", "/health/**", contentPathDir).permitAll()
.antMatchers("/**").authenticated();
}
//Add these two below.
#Override
public void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(apiKeyAuthenticationProvider());
}
#Bean
public TokenAuthenticationProvider apiKeyAuthenticationProvider() {
return new TokenAuthenticationProvider(apiKey);
}
I have a server that is just an API endpoint, no client front-end, no jsp, no html. It uses Spring Boot and I'm trying to secure it with Shiro. The relevent parts of my SpringBootServletInitializer look like this. I'm trying to get Shiro to return a 403 response if it fails the roles lookup as defined in BasicRealm. Yet it seems to default to redirecting to a non-existent login.jsp and no matter what solution I seem to use. I can't override that. Any help would be greatly appreciated.
#SpringBootApplication
public class RestApplication extends SpringBootServletInitializer {
...
#Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter() {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
Map<String, String> filterChain = new HashMap<>();
filterChain.put("/admin/**", "roles[admin]");
shiroFilter.setFilterChainDefinitionMap(filterChain);
shiroFilter.setSecurityManager(securityManager());
return shiroFilter;
}
#Bean
public org.apache.shiro.mgt.SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());
CookieRememberMeManager rmm = new CookieRememberMeManager();
rmm.setCipherKey(Base64.decode("XXXXXXXXXXXXXXXXXXXXXX"));
securityManager.setRememberMeManager(rmm);
return securityManager;
}
#Bean(name = "userRealm")
#DependsOn("lifecycleBeanPostProcessor")
public BasicRealm userRealm() {
return new BasicRealm();
}
#Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
public class BasicRealm extends AuthorizingRealm {
private static Logger logger = UserService.logger;
private static final String REALM_NAME = "BASIC";
public BasicRealm() {
super();
}
#Override
protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken token)
throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String userid = upToken.getUsername();
User user = Global.INST.getUserService().getUserById(userid);
if (user == null) {
throw new UnknownAccountException("No account found for user [" + userid + "]");
}
return new SimpleAuthenticationInfo(userid, user.getHashedPass().toCharArray(), REALM_NAME);
}
#Override
protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principals) {
String userid = (String) principals.getPrimaryPrincipal();
if (userid == null) {
return new SimpleAuthorizationInfo();
}
return new SimpleAuthorizationInfo(Global.INST.getUserService().getRoles(userid));
}
}
OK, here is how I solved it. I created a class ...
public class AuthFilter extends RolesAuthorizationFilter {
private static final String MESSAGE = "Access denied.";
#Override
protected boolean onAccessDenied(final ServletRequest request, final ServletResponse response) throws IOException {
HttpServletResponse httpResponse ;
try {
httpResponse = WebUtils.toHttp(response);
}
catch (ClassCastException ex) {
// Not a HTTP Servlet operation
return super.onAccessDenied(request, response) ;
}
if (MESSAGE == null) {
httpResponse.sendError(403);
} else {
httpResponse.sendError(403, MESSAGE);
}
return false; // No further processing.
}
}
... and then in my shiroFilter() method above I added this code ...
Map<String, Filter> filters = new HashMap<>();
filters.put("roles", new AuthFilter());
shiroFilter.setFilters(filters);
... hope this helps someone else.
In Shiro 1.4+ you can set the login url in your application.properties:
https://github.com/apache/shiro/blob/master/samples/spring-boot-web/src/main/resources/application.properties#L20
Earlier versions you should be able to set ShiroFilterFactoryBean.setLoginUrl("/login")
https://shiro.apache.org/static/current/apidocs/org/apache/shiro/spring/web/ShiroFilterFactoryBean.html
I'm using spring boot and i need to implement spring security with 3 fields authentication process username, password and corporate identifier as a hidden input in a form.
I implemented a custom usernamepasswordauthenticationfilter but it not seems to be enough to setup the security config.
EDIT :
Users don't seem to be authenticated ! because a can access to authenticated request defined in web config
EDIT 2 :
in my custom filter when a enter a valid user it's do execute on succesfulAuthentication. What i'm missing please provide me any help :(
Here were i am
#Repository
public class AuthenticationUserDetailsService implements UserDetailsService {
private static final Logger LOGGER = Logger.getLogger(AuthenticationUserDetailsService.class);
#Autowired
private UserRepository users;
private org.springframework.security.core.userdetails.User userdetails;
#Override
public UserDetails loadUserByUsername(String input) throws UsernameNotFoundException {
// TODO Auto-generated method stub
System.out.println(input);
String[] split = input.split(":");
if (split.length < 2) {
LOGGER.debug("User did not enter both username and corporate domain.");
throw new UsernameNotFoundException("no corporate identifier is specified");
}
String username = split[0];
String corporateId = split[1];
System.out.println("Username = " + username);
System.out.println("Corporate identifier = " + corporateId);
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
com.ubleam.corporate.server.model.User user;
user = checkUserDetail(username, corporateId);
if (user == null)
throw new NotAuthorizedException("Your are not allowed to access to this resource");
LOGGER.info("User email : " + user.getEmail() + "#User corporate : " + user.getCorporateId());
userdetails = new User(user.getEmail(), user.getPassword(), enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, getAuthorities("ROLE_USER"));
return userdetails;
}
/**
*
* #param roles
* roles granted for user
* #return List of granted authorities
*
*/
public List<GrantedAuthority> getAuthorities(String roles) {
List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
authList.add(new SimpleGrantedAuthority(roles));
return authList;
}
/**
* User authentication details from database
*
* #param username
* to use for authentication
* #param coporateId
* corporate identifier of user
* #return found user in database
*/
private com.ubleam.corporate.server.model.User checkUserDetail(String username, String corporateId) {
com.ubleam.corporate.server.model.User user = users.findByEmailAndCorporateId(username, corporateId);
return user;
}
My custom filter :
public class PlatformAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private static final Logger LOGGER = Logger.getLogger(PlatformAuthenticationFilter.class);
private static final String LOGIN_SUCCESS_URL = "{0}/bleamcards/{1}/home";
private static final String LOGIN_ERROR_URL = "{0}/bleamcards/{1}/login?error";
private String parameter = "corporateId";
private String delimiter = ":";
private String corporateId;
#Override
protected String obtainUsername(HttpServletRequest request) {
String username = request.getParameter(getUsernameParameter());
String extraInput = request.getParameter(getParameter());
String combinedUsername = username + getDelimiter() + extraInput;
setCorporateId(extraInput);
LOGGER.info("Combined username = " + combinedUsername);
return combinedUsername;
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException, ServletException {
String contextPath = request.getContextPath();
String url = MessageFormat.format(LOGIN_SUCCESS_URL, contextPath, corporateId);
response.sendRedirect(url);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
String contextPath = request.getContextPath();
String url = MessageFormat.format(LOGIN_ERROR_URL, contextPath, corporateId);
response.sendRedirect(url);
}
public String getParameter() {
return parameter;
}
public void setParameter(String corporateId) {
this.parameter = corporateId;
}
public String getDelimiter() {
return delimiter;
}
public void setDelimiter(String delimiter) {
this.delimiter = delimiter;
}
public String getCorporateId() {
return corporateId;
}
public void setCorporateId(String corporateId) {
this.corporateId = corporateId;
}
}
And finally the web security config :
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Inject
private AuthenticationManagerBuilder auth;
#Inject
private UserDetailsService userDS;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/bleamcards/**/login", "/bleamcards/**/forgetpassword", "/bleamcards/**/register", "/css/**", "/js/**", "/images/**", "/webjars/**")
.permitAll().anyRequest().authenticated().and().addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class).formLogin().loginPage("/login")
.defaultSuccessUrl("/").permitAll().and().logout().permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.eraseCredentials(false);
auth.userDetailsService(userDS).passwordEncoder(new BCryptPasswordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManager() throws Exception {
return auth.build();
}
#Bean
public PlatformAuthenticationFilter authenticationFilter() throws Exception {
PlatformAuthenticationFilter authFilter = new PlatformAuthenticationFilter();
authFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login", "POST"));
authFilter.setAuthenticationManager(authenticationManager());
authFilter.setUsernameParameter("username");
authFilter.setPasswordParameter("password");
authFilter.setParameter("corporateId");
return authFilter;
}
#Override
protected UserDetailsService userDetailsService() {
return userDS;
}
I want users to be able to connect only to /login /register /forgetpasswod urls for their respective corporate platforms
Actually i manage to find a solution to my issue.
I added successHandler on successfulAuthentication was missing ! And a failureHandler too on unsuccessfulAuthentication methods.
Here is my new Authentication filter :
public class TwoFactorAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private static final String LOGIN_SUCCESS_URL = "{0}/bleamcards/{1}/home";
private static final String LOGIN_ERROR_URL = "{0}/bleamcards/{1}/login?error";
private String parameter = "corporateId";
private String delimiter = ":";
private String corporateId;
#Override
protected String obtainUsername(HttpServletRequest request) {
String username = request.getParameter(getUsernameParameter());
String extraInput = request.getParameter(getParameter());
String combinedUsername = username + getDelimiter() + extraInput;
setCorporateId(extraInput);
System.out.println("Combined username = " + combinedUsername);
return combinedUsername;
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain , Authentication authResult) throws IOException, ServletException {
String contextPath = request.getContextPath();
String url = MessageFormat.format(LOGIN_SUCCESS_URL, contextPath, corporateId);
setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler(url));
super.successfulAuthentication(request, response, chain, authResult);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
String contextPath = request.getContextPath();
String url = MessageFormat.format(LOGIN_ERROR_URL, contextPath, corporateId);
setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler(url));
super.unsuccessfulAuthentication(request, response, failed);
}
public String getParameter() {
return parameter;
}
public void setParameter(String corporateId) {
this.parameter = corporateId;
}
public String getDelimiter() {
return delimiter;
}
public void setDelimiter(String delimiter) {
this.delimiter = delimiter;
}
public String getCorporateId() {
return corporateId;
}
public void setCorporateId(String corporateId) {
this.corporateId = corporateId;
}
}
Did you check that your AuthenticationUserDetailsService code is actually been executed? If the framework is not invoking it this means that your configuration is not properly hooking that UserDetailsService. In your WebSecurityConfig I think you need to have this:
#Bean
public AuthenticationManager getAuthenticationManager() throws Exception {
return super.authenticationManagerBean(); //not return auth.build();
}
I suggest you to take a look at this branch from Stormpath. There, they are configuring Spring Boot to use a custom AuthenticationProvider (similar to an UserDetailsService). That module uses and depends on this other Spring Security module.
Then, this sample Spring Security Example (note that it is not Spring Boot, but just Spring) will give you a complete example of the way the Spring Security Java Config is done. Please note that this Java Config extends this one which actually hides much of the actual internal configuration.
Disclaimer, I am an active Stormpath contributor.