1

i'm using Spring Boot2 as Framework and Thymeleaf as template engine.

in my authorization server, i added user 'admin' as 'ROLE_ADMIN'.

but in Client Application, when i loged in as 'admin' and print Authentication Object from SecurityContextHolder.getContext().getAuthentication(), Granted Authorities property has only 'ROLE_USER'.

following is my authorization server config.

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("admin").password(passwordEncoder().encode("123")).roles("USER", "ADMIN");
        auth
                .inMemoryAuthentication()
                .withUser("user").password(passwordEncoder().encode("123")).roles("USER");

    }

and following is Authentication Object from SecurityContextHolder.getContext().getAuthentication()'s logging code.

Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        System.out.println(auth.isAuthenticated());
        System.out.println(auth.getAuthorities());
        System.out.println(auth.getPrincipal());

and result is

//  isAuthenticated()
true

// getAuthorites()
[ROLE_USER] 

// getPrincipal()
Name: [admin], Granted Authorities: [ROLE_USER], User Attributes: [authorities=[{authority=ROLE_ADMIN}, {authority=ROLE_USER}], ...

following is my thymeleaf code.

            <div sec:authorize="isAuthenticated()">
                Text visible only to authenticated users.

                <!-- Principal name -->
                Authenticated username:
                <div sec:authentication="name"></div>

                <div sec:authorize="hasRole('USER')">Text visible to user.</div>
                <!-- i cant see this message -->
                <div sec:authorize="hasRole('ADMIN')">Text visible to admin.</div>

                Authenticated user roles:
                <!-- print '[ROLE_USER]' only -->
                <div sec:authentication="principal.authorities"></div>
            </div>

            <div sec:authorize="!isAuthenticated()">Text visible only to
                unauthenticated users.
            </div>

so, i want to access Principal.UserAttributes.authorities in thymeleaf.

i'm refering OAuth2AuthenticationToken, OAuth2User.getAttributes() and DefaultOAuth2User.toString()

how can i do this?

박예찬
  • 117
  • 1
  • 11
  • In your controller add the principal as argument e.g. `public String myController(Principal principal){ ... }` and then add it to your model map (or class) and use it then in thymeleaf. – Max R. May 06 '19 at 10:22
  • @MaxR. thanks for your answer. then, must i add ```Principal``` parameter to all of controller that include thymeleaf using thymeleaf-security? – 박예찬 May 06 '19 at 10:25
  • Yes, that would be one way. – Max R. May 06 '19 at 12:44
  • @MaxR. ok. i understood. but i don't know why my ```Principal``` Object don't have 'ROLE_ADMIN' yet. in authorization server's config, i added 'admin' user that has 'ROLE_ADMIN' – 박예찬 May 06 '19 at 13:32
  • You will need to extract the roles from your [authorities] and add them to Granted Authorities by yourself, one way is to use the AuthoritiesExtractor Interface, here is an example: https://www.baeldung.com/spring-security-oauth-principal-authorities-extractor (4.1 has an example implementation of the AuthoritiesExtractor) – Max R. May 06 '19 at 13:48

3 Answers3

3

I solved.

In Authorization Server, i configed like this.

  • AuthorizationServer WebSecurityConfigurerAdapter config
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    ...
        @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("admin").password(passwordEncoder().encode("123")).roles("USER", "ADMIN").authorities("USER", "ADMIN");
        auth
                .inMemoryAuthentication()
                .withUser("user").password(passwordEncoder().encode("123")).roles("USER");

    }
    ...
}

and following is my Resource Server's /me mapping controller

  • ResourceServer /me mapped Controller
@RestController
public class UserController {

    @RequestMapping("/me")
    public Principal user(Principal principal) {
        return principal;
    }
}

and following is my Client's WebSecurityConfigurerAdapter config

  • Client WebSecurityConfigurerAdapter config
@Configuration
@EnableOAuth2Client
public class WebSecurityConfigurerAdapterImpl extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/", "/home", "/error", "/webjars/**", "/resources/**", "/login**").permitAll()
                .anyRequest().authenticated()
                .and().oauth2Login();
    }

and in Client's Controller, i logged like this.

  • logging Principal in Client Controller
    @GetMapping("")
    public String git1() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        System.out.println(auth.getPrincipal());

        /** Thymeleaf using this **/
        Object authenticationProperty = AuthUtils.getAuthenticationProperty(auth, "principal.attributes['authorities']");
        System.out.println(authenticationProperty.toString());

        return VIEW_PATH + "git1";
    }

and following is the result

Name: [admin], Granted Authorities: [ROLE_USER], User Attributes: [authorities=[{authority=USER}, {authority=ADMIN}], details={remoteAddress=127.0.0.1, sessionId=null, tokenValue=82a7a532-a31e-4d0a-bd83-f15a9cbea3bc, tokenType=Bearer, decodedDetails=null}, authenticated=true, userAuthentication={authorities=[{authority=USER}, {authority=ADMIN}], details=null, authenticated=true, principal=admin, credentials=N/A, name=admin}, oauth2Request={clientId=foo, scope=[read], requestParameters={client_id=foo}, resourceIds=[], authorities=[], approved=true, refresh=false, redirectUri=null, responseTypes=[], extensions={}, refreshTokenRequest=null, grantType=null}, clientOnly=false, principal=admin, credentials=, name=admin]
[{authority=USER}, {authority=ADMIN}]

as you can see, i added 'ROLE_USER' and 'ROLE_ADMIN' Authorities in Authorization Server.

in Resource Server's Principal Object granted both 'ROLE_ADMIN' and 'ROLE_USER'.

but in Client's Principal Object doesn't granted 'ROLE_ADMIN'. there is 'ROLE_USER' Only.

and Principal.atttibutes['authorities'] has 'USER', 'ADMIN'.

as @Rahil Husain said, there is DefaultOAuth2UserService and this service grant 'ROLE_USER' only to OAuth2User Object.

first, i added CustomAuthoritiesExtractor via @Componenet annotation (@Bean too.) to Client.

but this doesn't working in my projects.

so, i implemented CustomOAuth2User and CustomOAuth2UserService.

like this.

  • CustomOAuth2User
public class CustomOAuth2User implements OAuth2User {
    private List<GrantedAuthority> authorities;
    private Map<String, Object> attributes;
    private String name;


    public CustomOAuth2User(List<GrantedAuthority> authorities, Map<String, Object> attributes) {
        this.authorities = authorities;
        this.attributes = attributes;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    @Override
    public Map<String, Object> getAttributes() {
        if (this.attributes == null) {
            this.attributes = new HashMap<>();
            this.attributes.put("name", this.getName());
        }
        return attributes;
    }

    @Override
    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

and following is CustomOAuth2UserService

  • CustomOAuth2UserService
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2User oAuth2User = super.loadUser(userRequest);

        AuthoritiesExtractor authoritiesExtractor = new CustomAuthoritiesExtractor();
        List<GrantedAuthority> grantedAuthorityList = authoritiesExtractor.extractAuthorities(oAuth2User.getAttributes());
        CustomOAuth2User customOAuth2User = new CustomOAuth2User(grantedAuthorityList, oAuth2User.getAttributes());
        customOAuth2User.setName(oAuth2User.getName());

        return customOAuth2User;
    }
}

and following is my CustomAuthoritiesExtractor. this class not used as @Bean or @Component. directly used in CustomOAuth2Service for mapping CustomOAuth2User object's authorities

  • CustomAuthoritiesExtractor
public class CustomAuthoritiesExtractor implements AuthoritiesExtractor {

    @Override
    public List<GrantedAuthority> extractAuthorities(Map<String, Object> map) {
        return AuthorityUtils.commaSeparatedStringToAuthorityList(asAuthorities(map));
    }

    private String asAuthorities(Map<String, Object> map) {
        List<String> authorities = new ArrayList<>();
        List<LinkedHashMap<String, String>> authz =
                (List<LinkedHashMap<String, String>>) map.get("authorities");
        for (LinkedHashMap<String, String> entry : authz) {
            authorities.add(entry.get("authority"));
        }
        return String.join(",", authorities);
    }
}

and final, i changed Client's endpoint to using my CustomOAuth2User and CustomOAuth2UserService.

so, i changed Client's WebSecurityConfigurerAdapter config like this.

@Configuration
@EnableOAuth2Client
public class WebSecurityConfigurerAdapterImpl extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/", "/home", "/error", "/webjars/**", "/resources/**", "/login**").permitAll()
                .anyRequest().authenticated()
                .and().oauth2Login()


                /** add this config**/
                            .userInfoEndpoint()
                                    .customUserType(CustomOAuth2User.class, "teemo")
                                    .userService(this.oauth2UserService());
    }

    private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
        return new CustomOAuth2UserService();
    }

and following is my thymeleaf.

  • thymeleaf
    <div sec:authorize="isAuthenticated()">
        Text visible only to authenticated users.

        Authenticated username:
        <div sec:authentication="name"></div>

        <div sec:authorize="hasRole('USER')">hasRole('USER')</div>
        <div sec:authorize="hasRole('ROLE_USER')">hasRole('ROLE_USER')</div>
        <div sec:authorize="hasRole('ADMIN')">hasRole('ADMIN')</div>
        <div sec:authorize="hasRole('ROLE_ADMIN')">hasRole('ROLE_ADMIN')</div>
        <!-- TRUE -->
        <div sec:authorize="hasAuthority('USER')">hasAuthority('USER')</div>
        <div sec:authorize="hasAuthority('ROLE_USER')">hasAuthority('ROLE_USER')</div>
        <!-- TRUE -->
        <div sec:authorize="hasAuthority('ADMIN')">hasAuthority('ADMIN')</div>
        <div sec:authorize="hasAuthority('ROLE_ADMIN')">hasAuthority('ROLE_ADMIN')</div>
    </div>

    <div sec:authorize="!isAuthenticated()">Text visible only to
                unauthenticated users.
    </div>

and following is the result.

Text visible only to authenticated users. Authenticated username:
admin
hasAuthority('USER')
hasAuthority('ADMIN')

anyone who digging like me, i hope help this question and answers.

but i don't know this is de facto-standard way.

just.. working now.

박예찬
  • 117
  • 1
  • 11
  • wow this was amazing, but i had replace `asAuthorities` method with `String authoritiesString= map.get("authorities").toString(); authoritiesString = authoritiesString.substring(1, authoritiesString.length() - 1); authoritiesString = authoritiesString.trim(); return authoritiesString;` as casting to LinkedHashMap didn't work. but thanks you saved my day – Priyamal Oct 03 '19 at 07:24
1

Use #authentication Object

<div th:text="${#authentication.principal.something}"> The value of the "name" property of the authentication object should appear here. </div>

Example:

<img th:if="${#authentication.principal.image}"
class="img-circle" th:src="${#authentication.principal.image}"
width="100" height="100" alt="place-holder" />

But Add this dependency first

 <dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
    <version>-latest-version-here-</version>
</dependency>

As it does not come with thymeleaf-starter in spring boot

Rahil Husain
  • 570
  • 3
  • 14
  • thanks for your answer. i added ```thymeleaf-extras-springsecurity5``` dependency already. what i want to saying is 'why my principal object's authority has only 'ROLE_USER'. – 박예찬 May 07 '19 at 13:49
  • See the implementation of [DefaultOAuth2UserService](https://github.com/spring-projects/spring-security/blob/master/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java). The default implementation always creates Prinicpal object with USER_ROLE authority. You have to provide your own implementation of [OAuth2UserService](https://github.com/spring-projects/spring-security/blob/master/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/OAuth2UserService.java) – Rahil Husain May 07 '19 at 14:32
  • `Set authorities = Collections.singleton(new OAuth2UserAuthority(userAttributes));` `public OAuth2UserAuthority(Map attributes) { this("ROLE_USER", attributes); }` – Rahil Husain May 07 '19 at 14:32
0

You can pass the Principal to your controller as argument, like

public String myController(Principal principal) { 
   ... 
}

You will also have to map the authorities to granted Authorities by yourself, e.g. using the AuthoritiesExtractor Interface from Spring, here is an example: Link from Baeldung

Max R.
  • 1,574
  • 2
  • 12
  • 27
  • thanks for your answer. i dont want to implement Parameterized Controller. there are too many controllers that using this feature. but as you said, I will check ```AuthoritiesExtractor```. thank you. – 박예찬 May 07 '19 at 13:50
  • i'm looking for ```AuthoritiesExtractor```. my project is composed of ```Client```, ```Authorization Server``` and ```Resource Server```. these projects are separated. what project implement ```CustomAuthoritiesExtractor```? I added All project this, but not working. in ```Resource Server```, i logging ```Principal``` or ```Authentication``` Object where mapping "/me" endpoint. and there are 'ROLE_USER', 'ROLE_ADMIN' in ```Granted Authorities``` properly. i guess something is wrong when get ```Granted Authorities``` in Client – 박예찬 May 08 '19 at 01:01