【问题标题】:Role Based Security Based on Jsonwebtoken using different roles for different controllers in Spring Boot基于Jsonwebtoken的基于角色的安全性在Spring Boot中为不同的控制器使用不同的角色
【发布时间】:2018-05-27 22:02:00
【问题描述】:

我成功地对用户进行身份验证、获取 jsonwebtoken 并访问受保护的 url。我以“用户”或“管理员”的角色坚持对令牌的声明。理想情况下,在验证用户之后,我希望能够根据用户角色/角色保留角色,并根据这些保护我的 api 中的特定 url。我有以下设置。如何将安全性应用于特定 url 以根据令牌中的角色进行区分?

Package com.vicentex.api;

// imports excluded for brevity

 @SpringBootApplication
 public class VicentexTradingApiApplication {

 @Bean
 public FilterRegistrationBean jwtFilter() {
    final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
    registrationBean.setFilter(new JwtFilter());
    registrationBean.addUrlPatterns("/api/*");

    return registrationBean;
 }

 public static void main(String[] args) {
    SpringApplication.run(VicentexTradingApiApplication.class, args);
 }
}

/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////

 @RestController
 public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping(value = "/api/users", method = RequestMethod.GET)
    List<User> getAllUsers(){
       return this.userService.getAllUsers();
    }

    @RequestMapping(value = "/api/users/{userName}",method = RequestMethod.GET)
    User getUser(@PathVariable String userName) {
       return this.userService.getUser(userName);
    }

    @RequestMapping(value = "/api/users", method = RequestMethod.POST)
    void addUser(@RequestBody User user) {
       this.userService.addUser(user);
    }

    @RequestMapping(value = "/api/users", method = RequestMethod.PUT)
    void updateUser(@RequestBody User user) {
       this.userService.updateUser(user);
    }


    @RequestMapping(value = "/api/users/{userName}", method = RequestMethod.DELETE)
    void deleteUser(@PathVariable String userName) {
       this.userService.deleteUser(userName);
    }

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public User registerUser(@RequestBody User user) {
       userService.addUser(user);
       return user;
    }


    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String login(@RequestBody User login) throws ServletException {

        System.out.println("Controller Secret: " + Constants.secretKey);

        String jwtToken = "";

        if (login.getUserName() == null || login.getPassword() == null) {
           throw new ServletException("Please fill in username and password");
        }

        String userName = login.getUserName();
        String password = login.getPassword();

        User user = userService.Authenticate(userName, password);


        if (user == null) {
           throw new ServletException("User not found.");
        }

        String pwd = user.getPassword();

        if (!password.equals(pwd)) {
           throw new ServletException("Invalid login. Please check your name and password.");
        }


        if (user.isAdmin()) {

           jwtToken = Jwts.builder()
                .setSubject(userName)
                .claim("roles", "admin")
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS256, Constants.secretKey)
                .compact();
        } else {

           jwtToken = Jwts.builder()
                .setSubject(userName)
                .claim("roles", "user")
                .setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS256, Constants.secretKey)
                .compact();

        }

        return jwtToken;
    }


  }

  ////////////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////

  package com.vicentex.api.models;

  @Document(collection = "Users")
  public class User {

    @Id 
    private ObjectId id;
    private String userName;
    private String fullName;
    private String email;
    private String password;
    private String image;
    private boolean isAdmin = false; //flagged for simplicity. Will be an array

    Account acct;

    @DBRef
    List<Transaction> transactions;


    public User() {
       super();
    }

    public User(String userName, String fullName, String email, String password, String image) {
       super();
       this.userName = userName;
       this.fullName = fullName;
       this.email = email;
       this.password = password; //note to remember to encrypt on creation
       this.image = image;
    }


    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getFullName() {
        return fullName;
    }
    public void setFullName(String fullName) {
        this.fullName = fullName;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public String getImage() {
        return image;
    }
    public void setImage(String image) {
        this.image = image;
    }

    public boolean isAdmin() {
        return isAdmin;
    }

    public void setAdmin(boolean isAdmin) {
        this.isAdmin = isAdmin;
    }

    public Account getAcct() {
        return acct;
    }

    public void setAcct(Account acct) {
        this.acct = acct;
    }

    public List<Transaction> getTransactions() {
        return transactions;
    }

    public void setTransactions(List<Transaction> transactions) {
        this.transactions = transactions;
    }

 }

【问题讨论】:

    标签: java spring json-web-token


    【解决方案1】:

    为了使用@PreAuthorize,我必须在我的 pom.xml 中包含 Spring Security 作为依赖项。这迫使我拥有 API 的密码,这不是我想要的。更糟糕的是,如果我禁用了密码,它基本上就达不到目的了,因为其他方法都不起作用。我只是想利用 jsonwebtoken 作为 https 上的安全机制,并根据令牌中的角色保护 url 的特定子集。所以,我实际上最终做的是:

    public class JwtFilter extends GenericFilterBean {
    
      public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain)
            throws IOException, ServletException {
    
        System.out.println("Secret Key: " + Constants.secretKey);
    
        final HttpServletRequest request = (HttpServletRequest) req;
        final HttpServletResponse response = (HttpServletResponse) res;
        final String authHeader = request.getHeader("authorization");
    
        if ("OPTIONS".equals(request.getMethod())) {
    
            response.setStatus(HttpServletResponse.SC_OK);
            chain.doFilter(req, res);
    
        } else {
    
            if (authHeader == null || !authHeader.startsWith("Bearer ")) {
                throw new ServletException("Missing or invalid Authorization header");
            }
    
            final String token = authHeader.substring(7);
    
            try {
    
                final Claims claims = Jwts.parser().setSigningKey(Constants.secretKey).parseClaimsJws(token).getBody();
    
                String[] roles = claims.get("roles").toString().split(",");
    
                if (request.getRequestURI().contains("/api/")) {
                    if (!Arrays.asList(roles).contains("user")) {
                        System.out.println("ROLES: " + claims.get("roles"));
                        throw new ServletException("A minimum security credential of 'user' is required to access this resource!");
                    }
                }
    
                if (request.getRequestURI().contains("/api/admin")) {
                    if (!Arrays.asList(roles).contains("admin")) {
                        System.out.println("ROLES: " + claims.get("roles"));
                        throw new ServletException("This route is restricted to administrators only");
                    }
                }
    
    
                request.setAttribute("claims", claims);
    
            } catch (final SignatureException e) {
                throw new ServletException("Invalid token");
            }
    
            chain.doFilter(req, res);
         }
       }
     }
    
     ///////////////////////////////////////////////////////////////
     ///////////////////////////////////////////////////////////////
     // In addition, I changed the login code in the user controller
    
     if (user.isAdmin()) {
    
            jwtToken = Jwts.builder()
                    .setSubject(userName)
                    .claim("roles", "admin,user")
                    .setIssuedAt(new Date())
                    .signWith(SignatureAlgorithm.HS256, Constants.secretKey)
                    .compact();
        } else {
    
            jwtToken = Jwts.builder()
                    .setSubject(userName)
                    .claim("roles", "user")
                    .setIssuedAt(new Date())
                    .signWith(SignatureAlgorithm.HS256, Constants.secretKey)
                    .compact();
    
        }
    

    从技术上讲,我可以使用角色/uri 的安全映射创建一个集合,并根据请求的 URI 循环遍历每个集合,而无需装饰控制器。

    【讨论】:

      【解决方案2】:

      在 Spring 中,您将在方法级别使用 @PreAuthorize( "hasRole('somrole')" ) 注释。 Spring 期望该角色属于“权威”声明。

      【讨论】:

      • 所以,如果我理解正确,那么我需要将:.claim("roles", "user") 更改为 .claim("roles", authority),权限为 String[ ] 表示角色,然后根据所需的访问权限装饰任何控制器???
      • 是的,它是一个角色数组。不确定您的电话 claim("authorities", "user") 是否会放入数组中。
      猜你喜欢
      • 2017-04-04
      • 1970-01-01
      • 2013-03-21
      • 2011-05-12
      • 1970-01-01
      • 1970-01-01
      • 2012-03-18
      • 2014-07-20
      • 2011-08-19
      相关资源
      最近更新 更多