使用Spring Security时遇到的角色和权限问题

huangapple go评论79阅读模式
英文:

problems using Roles and Permissions using Spring security

问题

我正在使用Spring Security 3。我的目标是根据角色和权限为特定用户授予特定操作(控制器方法)。

ROLE_ADMIN -> 创建、读取、更新和删除
ROLE_USER -> 读取和更新

当我注册一个用户时,它会以加密密码的用户对象保存到数据库中,没有问题。但是当我使用Postman使用基本身份验证命中控制器API时,它显示状态码200 OK,但不是预期的JSON输出。然后,在遇到这个问题后,当我使用Google Chrome访问相同的URL时,它显示状态码404 NOT FOUND的白标签错误页面。

这是我的SecurityConfig.java文件:

@Configuration
//@Import({GsonConfig.class})
@EnableMethodSecurity
@EnableWebSecurity(debug = true)
public class SecurityConfig {

@Autowired
ProductUserRepository productuserrepo;

@Bean
public PasswordEncoder encoder() { 
    return new BCryptPasswordEncoder();
}

@Bean
public UserDetailsService userDetailsService() {
    return username -> productuserrepo.findByUsername(username)
        .orElseThrow(() -> new UsernameNotFoundException("User not found"));
}
  
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
  return http.csrf(csrf -> csrf.disable()) 
             .cors(cors -> cors.disable())
             .authorizeHttpRequests(a -> {
                                   a.requestMatchers("/auth/**").permitAll();
                                       a.anyRequest().authenticated();   
               })
               .formLogin(login -> {
                            login.loginProcessingUrl("/login")
                                 .failureForwardUrl("/loginfailure");
               })
               .logout(logout ->{
                        logout.logoutUrl("/logout")
                              .deleteCookies("JSESSIONID")
                              .invalidateHttpSession(false)
                              .logoutSuccessUrl("/successlogout")
                              .logoutSuccessHandler((request, response, authentication) -> SecurityContextHolder.clearContext());
            }) 
               .authenticationManager(authManager()) 
               .build();
} 

@Bean
public AuthenticationManager authManager(){
    DaoAuthenticationProvider daoProvider = new DaoAuthenticationProvider();
    daoProvider.setUserDetailsService(userDetailsService());
    daoProvider.setPasswordEncoder(encoder());
    return new ProviderManager(daoProvider);
}

}

这是我的Role.java文件(使用List获取权限列表):

@RequiredArgsConstructor
public enum Role {

USER(Set.of(USER_READ,USER_UPDATE)),
  
ADMIN(Set.of(ADMIN_READ,ADMIN_UPDATE,ADMIN_DELETE,ADMIN_CREATE,
               USER_READ,USER_UPDATE));
 
@Getter
private final Set<Permission> permissions;

public  List<SimpleGrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = getPermissions().stream()
                                                                    .map(permission -> new SimpleGrantedAuthority(permission.getPermission()))
                                                                    .collect(Collectors.toList());
        authorities.add(new SimpleGrantedAuthority("ROLE_" + this.name()));
        return authorities;
}

}

这是我的Permission.java文件:

public enum Permission {

ADMIN_READ("admin:read"),
ADMIN_UPDATE("admin:update"),
ADMIN_CREATE("admin:create"),
ADMIN_DELETE("admin:delete"),
USER_READ("user:read"),
USER_UPDATE("user:update");

@Getter
private final String permission;

}

最后,这是我的UserController用于读取资源的代码:

@RestController
@RequestMapping("/user")
@PreAuthorize("hasAnyRole('USER','ADMIN')")
public class UserController {

@PreAuthorize("hasAnyAuthority('admin:read','user:read')")
@GetMapping(value = "/findproduct",produces = MediaType.APPLICATION_JSON_VALUE,
        headers = "Accept=application/json",consumes = MediaType.APPLICATION_JSON_VALUE)
public Product findproductbyid(@RequestParam("productid")int productid) {
    System.out.println("inside findproduct method");
    return prodrepo.findById(productid).get(); 
}

}

还有我的AuthController用于注册:

@RestController
@RequestMapping("/auth")
public class AuthController {

@Autowired
ProductUserRepository produserrepo;

@Autowired
PasswordEncoder pwd_encoder;

@Autowired
Gson gson;

@PostMapping(value = "/register",produces = MediaType.APPLICATION_JSON_VALUE,
headers = "Accept=application/json",consumes = MediaType.APPLICATION_JSON_VALUE)
public String register(@RequestBody ProductUserModel user) {

if (produserrepo.existsByUsername(user.getUsername())) {  
    return user.getUsername() + " already taken";
}else { 
    System.out.println(user.toString());
        user.setPassword(pwd_encoder.encode(user.getPassword()));  
        produserrepo.save(gson.fromJson(gson.toJson(user), ProductUser.class));
        return user.getUsername() + " succesfully registered";
}

}

我的唯一问题是:当我使用Google Chrome访问http://localhost:8081/user/findproduct?productid=1时,会重定向到登录页面。然后,如果我使用我成功注册并保存到数据库的用户名/密码凭据,它会显示状态码404 NOT FOUND的白标签错误页面。

有人有任何解决此问题的想法吗?

英文:

I'm using Spring Security 3.
My objective is to grant specific operations(controller methods) based on roles and permissions with a particular user.

ROLE_ADMIN -> create,read,update and delete
ROLE_USER -> read and update.

When I register a user, it is saved as a user object to a database with encrypted password with no issue. But when I hit the controller api using UserController with basic authentication using Postman, it is showing status 200 OK but not the expected json output. Then after facing that when I hit the same url with Google Chrome, it is showing Whitelabel error page with status 404 NOT FOUND.

Here is my SecurityConfig.java

@Configuration
//@Import({GsonConfig.class}) 
@EnableMethodSecurity 
@EnableWebSecurity(debug = true)
public class SecurityConfig {
	
	@Autowired
	ProductUserRepository productuserrepo;

	@Bean
	public PasswordEncoder encoder() { 
		return new BCryptPasswordEncoder();
	}
	
	@Bean
	public UserDetailsService userDetailsService() {
	    return username -&gt; productuserrepo.findByUsername(username)
	        .orElseThrow(() -&gt; new UsernameNotFoundException(&quot;User not found&quot;));
	}
	  
	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
	  return http.csrf(csrf -&gt; csrf.disable()) 
				 .cors(cors -&gt; cors.disable())
			     .authorizeHttpRequests(a -&gt; {
									       a.requestMatchers(&quot;/auth/**&quot;).permitAll();
										   a.anyRequest().authenticated();   
				   })
				   .formLogin(login -&gt; {
					   			login.loginProcessingUrl(&quot;/login&quot;)
					   				 .failureForwardUrl(&quot;/loginfailure&quot;);
				   })
				   .logout(logout -&gt;{
							logout.logoutUrl(&quot;/logout&quot;)
								  .deleteCookies(&quot;JSESSIONID&quot;)
								  .invalidateHttpSession(false)
								  .logoutSuccessUrl(&quot;/successlogout&quot;)
								  .logoutSuccessHandler((request, response, authentication) -&gt; SecurityContextHolder.clearContext());
					}) 
				   .authenticationManager(authManager()) 
				   .build();
	} 
	
    @Bean
    public AuthenticationManager authManager(){
        DaoAuthenticationProvider daoProvider = new DaoAuthenticationProvider();
        daoProvider.setUserDetailsService(userDetailsService());
        daoProvider.setPasswordEncoder(encoder());
        return new ProviderManager(daoProvider);
    }

}

Here is my Role.java [getting list of permissions using List&lt;SimpleGrantedAuthority&gt;]

@RequiredArgsConstructor
public enum Role {

	USER(Set.of(USER_READ,USER_UPDATE)),
	  
	ADMIN(Set.of(ADMIN_READ,ADMIN_UPDATE,ADMIN_DELETE,ADMIN_CREATE,
	               USER_READ,USER_UPDATE));
	 
	@Getter
	private final Set&lt;Permission&gt; permissions;
	
	public  List&lt;SimpleGrantedAuthority&gt; getAuthorities() {
		    List&lt;SimpleGrantedAuthority&gt; authorities = getPermissions().stream()
															            .map(permission -&gt; new SimpleGrantedAuthority(permission.getPermission()))
															            .collect(Collectors.toList());
		    authorities.add(new SimpleGrantedAuthority(&quot;ROLE_&quot; + this.name()));
		    return authorities;
	}
}

Here is my Permission.java

public enum Permission {

    ADMIN_READ(&quot;admin:read&quot;),
    ADMIN_UPDATE(&quot;admin:update&quot;),
    ADMIN_CREATE(&quot;admin:create&quot;),
    ADMIN_DELETE(&quot;admin:delete&quot;),
    USER_READ(&quot;user:read&quot;),
    USER_UPDATE(&quot;user:update&quot;);

    @Getter
    private final String permission;
}

Finally here is my user controller to read resource

@RestController
@RequestMapping(&quot;/user&quot;)
@PreAuthorize(&quot;hasAnyRole(&#39;USER&#39;,&#39;ADMIN&#39;)&quot;)
public class UserController {
	
	@PreAuthorize(&quot;hasAnyAuthority(&#39;admin:read&#39;,&#39;user:read&#39;)&quot;)
	@GetMapping(value = &quot;/findproduct&quot;,produces = MediaType.APPLICATION_JSON_VALUE,
			headers = &quot;Accept=application/json&quot;,consumes = MediaType.APPLICATION_JSON_VALUE)
	public Product findproductbyid(@RequestParam(&quot;productid&quot;)int productid) {
		System.out.println(&quot;inside findproduct method&quot;);
		return prodrepo.findById(productid).get(); 
	}
}

And my AuthController to register :

@RestController
@RequestMapping(&quot;/auth&quot;)
public class AuthController {

@Autowired
ProductUserRepository produserrepo;

@Autowired
PasswordEncoder pwd_encoder; 

@Autowired
Gson gson;


@PostMapping(value = &quot;/register&quot;,produces = MediaType.APPLICATION_JSON_VALUE,
		headers = &quot;Accept=application/json&quot;,consumes = MediaType.APPLICATION_JSON_VALUE)
public String register(@RequestBody ProductUserModel user) {
	
	if (produserrepo.existsByUsername(user.getUsername())) {  
		return user.getUsername() + &quot; already taken&quot;;
	}else { 
		System.out.println(user.toString());
			user.setPassword(pwd_encoder.encode(user.getPassword()));  
			produserrepo.save(gson.fromJson(gson.toJson(user), ProductUser.class));
			return user.getUsername() + &quot; succesfully registered&quot;;
	}
}

My only issue is :
when I hit http://localhost:8081/user/findproduct?productid=1 for UserController using Google Chrome, then login page redirected. Then if I hit username/password credentials which I successfully registered and saved to database, it is showing Whitelabel error page with status 404 NOT FOUND.

Can anyone has any idea to fix the issue?

答案1

得分: 2

好的,以下是翻译好的内容:

好的,我自己解决了。

首先,我在AuthController中添加了登录方法:

@GetMapping(value = "/login", produces = MediaType.APPLICATION_JSON_VALUE,
            headers = "Accept=application/json", consumes = MediaType.APPLICATION_JSON_VALUE)
public void login(HttpServletRequest req, @RequestHeader(HttpHeaders.AUTHORIZATION) String authorization) {
    
    if (authorization != null && authorization.toLowerCase().startsWith("basic")) {
        // Authorization: Basic base64credentials
        String base64Credentials = authorization.substring("Basic".length()).trim();
        byte[] credDecoded = Base64.getDecoder().decode(base64Credentials); 
        String credentials = new String(credDecoded, StandardCharsets.UTF_8);
        // credentials = username:password
        final String[] values = credentials.split(":", 2);
        String username = values[0];
        String password = values[1];
        authenticateusercredentials(username, password, req);
    } else {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        authenticateusercredentials(username, password, req);
    }
}
 
public void authenticateusercredentials(String username, String password, HttpServletRequest req) {
    UsernamePasswordAuthenticationToken authReq = new UsernamePasswordAuthenticationToken(username, password);
    Authentication auth = authmanager.authenticate(authReq);
    SecurityContext sc = SecurityContextHolder.getContext();
    sc.setAuthentication(auth);
    HttpSession session = req.getSession(true);
    session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, sc);
}

其次,我在Security Configuration类中将/login更改为/auth/login:

...

.formLogin(login -> {
                login.loginProcessingUrl("/auth/login")
                     .failureForwardUrl("/loginfailure");
           })
...

谢谢和祝好

<details>
<summary>英文:</summary>

OK I resolved by myself.

Firstly, I add login method inside AuthController

    @GetMapping(value = &quot;/login&quot;,produces = MediaType.APPLICATION_JSON_VALUE,
    			headers = &quot;Accept=application/json&quot;,consumes = MediaType.APPLICATION_JSON_VALUE)
    	public void login(HttpServletRequest req,@RequestHeader(HttpHeaders.AUTHORIZATION) String authorization) {
    		
    		if (authorization != null &amp;&amp; authorization.toLowerCase().startsWith(&quot;basic&quot;)) {
    		    // Authorization: Basic base64credentials
    		    String base64Credentials = authorization.substring(&quot;Basic&quot;.length()).trim();
    		    byte[] credDecoded = Base64.getDecoder().decode(base64Credentials); 
    		    String credentials = new String(credDecoded, StandardCharsets.UTF_8);
    		    // credentials = username:password
    		    final String[] values = credentials.split(&quot;:&quot;, 2);
    		    String username = values[0];
    		    String password = values[1];
    		    authenticateusercredentials(username, password, req);
    		}else {
    			String username = req.getParameter(&quot;username&quot;);
    		    String password = req.getParameter(&quot;password&quot;);
    		    authenticateusercredentials(username, password, req);
    		}
    	}
    	 
    	public void authenticateusercredentials(String username,String password,HttpServletRequest req) {
            UsernamePasswordAuthenticationToken authReq = new UsernamePasswordAuthenticationToken(username, password);
            Authentication auth = authmanager.authenticate(authReq);
            SecurityContext sc = SecurityContextHolder.getContext();
            sc.setAuthentication(auth);
            HttpSession session = req.getSession(true);
            session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, sc);
    	}
    
Secondly I changed /login to auth/login in Security Configuration class

    ...
    
    .formLogin(login -&gt; {
    					   			login.loginProcessingUrl(&quot;/auth/login&quot;)
    					   				 .failureForwardUrl(&quot;/loginfailure&quot;);
    				   })
    ...
    
Thanks and Regards

</details>



huangapple
  • 本文由 发表于 2023年8月8日 21:36:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/76860093.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定