Simplified Spring Security with Grails 是一个简化的Spring Security插件,专为Grails框架设计

Simplified Spring Security with Grails 是一个简化的Spring Security插件,专为Grails框架设计。它提供了一种简单而强大的方式来保护Grails应用程序,通过集成Spring Security的核心功能,同时保持与Grails生态系统的兼容性。这个插件旨在减少开发人员在实现安全性时的复杂性,提供了一系列易于使用的功能,如用户认证、授权、加密和会话管理等。

主要特点包括:

  1. 快速集成:通过简单的配置即可将Spring Security的强大功能集成到Grails应用中,无需编写大量代码。

  2. 用户认证:支持多种认证方式,包括但不限于用户名密码认证、OAuth2等。

  3. 角色和权限管理:允许定义细粒度的角色和权限,以控制不同用户对资源的访问。

  4. 加密服务:提供数据加密功能,确保敏感信息的安全存储和传输。

  5. 会话管理:支持自定义会话策略,增强应用的安全性。

  6. 易于扩展:可以通过插件机制轻松添加新的特性或定制现有功能。
    使用Grails简化Spring Security进行用户认证,可以通过以下几个步骤来实现:

  7. 添加依赖:首先,在你的build.gradle文件中添加Spring Security的依赖。对于Grails 4及以上版本,可以使用以下代码片段:

    implementation 'org.springframework.boot:spring-boot-starter-security'
    
  8. 配置安全设置:创建一个新的配置文件application.yml(如果还没有的话),并在其中添加Spring Security的基本配置。例如:

    spring:
      security:
        user:
          name: admin
          password: admin
        basic:
          enabled: true
    
  9. 创建用户详情服务:实现一个UserDetailsService接口,用于加载用户特定的数据。这通常涉及到查询数据库以获取用户信息。你可以使用内存中的用户存储来简化示例:

    @Service
    public class InMemoryUserDetailsManager implements UserDetailsService {
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            // 这里简单返回一个内存中的用户对象
            return new User("admin", "{noop}" + "password", new ArrayList<>());
        }
    }
    
  10. 配置Web安全性:通过扩展WebSecurityConfigurerAdapter类,你可以自定义请求的安全规则。例如,可以设置哪些URL需要认证,哪些不需要:

    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                    .antMatchers("/", "/home").permitAll()
                    .anyRequest().authenticated()
                    .and()
                .formLogin()
                    .loginPage("/login")
                    .permitAll()
                    .and()
                .logout()
                    .permitAll();
        }
    }
    
  11. 运行你的应用:现在,当你的应用运行时,它将要求用户在访问受保护的资源之前进行登录。用户将看到一个简单的登录页面,输入用户名和密码后才能访问。

通过以上步骤,你可以在Grails应用中利用Spring Security进行基本的用户认证。这只是一个入门级的示例,实际应用中你可能需要处理更复杂的情况,如数据库集成、角色管理等。

Spring Security is a powerful library for securing your applications that comes with a bewildering number of options. Based on Spring, it can be readily integrated into a Grails application. But why not save the hassle and use the new improved Grails plugin?

The plugin has gone through several evolutionary stages that started with the Acegi plugin. Its most recent incarnation is a complete rewrite for Spring Security 3 and Spring 3. One of the results of this is that the plugin will only work with Grails 1.2.2 and above. Another significant change is that there is no longer just one Spring Security plugin: some features have been broken out into optional plugins. So now you only include the features you need in your application.

So what do the plugins give you? The core provides the basics necessary for access control in an easy-to-use package based on users and roles. In fact, many applications won’t need any other plugin than the core one. For those who do need something extra, here is a list of the other plugins in the family:

OpenID - authentication using OpenID
LDAP - authentication against LDAP servers
CAS - single sign-on using CAS
ACLs - access control via Spring Security's ACLs
UI - user interface for user and role managment, plus other features

In this article I’ll show you how to secure a Grails application from scratch using the new core plugin.

Update This article now has two companion screencasts:
[caption id=“attachment_5509” align=“center” width=“250” caption=“Spring Security Plugin Introduction”][/caption] [caption id=“attachment_5510” align=“center” width=“250” caption=“Spring Security Plugin - AJAX”][/caption]
Setup

As with most plugins, your first step will be to install the Spring Security plugin. Of course, you’ll need a project to install it into and for this article I have provided a simple Twitter-clone called Hubbub (based on the sample application from Grails in Action). You can also get the finished project from here.

So, from within your project, run:

grails install-plugin spring-security-core

If you look at the output generated by the plugin installation, you will see that it provides a couple of commands. The most important of these is s2-quickstart, which will help you get up and running with the minimum of fuss. It generates both the basic domain classes you need to store user information and the controllers that handle authentication.

Before you run the command, you may need to make a decision. If you already have a ‘user’ domain class, you will have to decide how to integrate it with the one generated by the plugin. One option is to replace the existing domain class and simply apply your customisations to the replacement. The other approach consists of making your own domain class extend the plugin’s one.

Which is better? I prefer the latter because it allows you to easily update the generated user domain class if its template ever changes. It also means you don’t overly pollute your domain model with Spring Security specifics. On the downside, you have to deal with domain class inheritance, although the cost is pretty minimal.

For Hubbub, we’ll make the user domain class extend the generated one, which means we should use domain class names that don’t conflict with the existing ones:

grails s2-quickstart org.example SecUser SecRole

This will create three domain classes for us:

org.example.SecUser

org.example.SecRole

org.example.SecUserSecRole - links users to roles

and two controllers:

LoginController

LogoutController

plus their associated views. In just two commands, we have everything we need to start securing our application!

The sample application does need one more change: its URL mappings mean that the login and logout controllers can’t be reached. That’s simple enough to fix by adding the following two lines to UrlMappings.groovy:

“/login/ a c t i o n ? " ( c o n t r o l l e r : " l o g i n " ) " / l o g o u t / action?"(controller: "login") "/logout/ action?"(controller:"login")"/logout/action?”(controller: “logout”)

If you don’t make the change, the login page will generate a 404 error! Now let’s get down to the business of protecting the application.
Adding access control

The whole point of this exercise is to limit access to certain parts of the application. For web applications this most commonly means protecting particular pages, or more specifically URLs. In the case of Hubbub, we have the following requirements:

The home page is accessible to everyone - /
Only known users can see the posts for a particular user - /person/<username>
Only users with a 'user' role should be able to access their own timeline - /timeline
Same goes for following another user - /post/followAjax
Only fully authenticated users with the 'user' role should be able to post a new message - /post/addPostAjax

With the Spring Security plugin this is trivial to achieve, although you do have to make a decision on which of three mechanisms to use. You can take a controller-centric approach and annotate the actions; work with static URL rules in Config.groovy; or define runtime rules in the database using request maps.
Annotations

For a controller-centric approach, you can’t beat the @Secured annotation provided by the plugin. In it’s simplest incarnation, you pass it a list of basic rules that define who can access the corresponding action. Here, I apply Hubbub’s access control rules via annotations on the post controller:

package org.example

import grails.plugins.springsecurity.Secured

class PostController {

@Secured([‘ROLE_USER’])
def followAjax = { … }

@Secured(['ROLE_USER', 'IS_AUTHENTICATED_FULLY'])
def addPostAjax = { ... }

def global = { ... }

@Secured(['ROLE_USER'])
def timeline = { ... }

@Secured(['IS_AUTHENTICATED_REMEMBERED'])
def personal = { ... }

}

The IS_AUTHENTICATED_* rules are built into Spring Security, but ROLE_USER is a role that must exist in the database - something we have yet to do. Also, if you specify more than one rule in the list, then the current user normally only has to satisfy one of them - as is explained in the user guide. IS_AUTHENTICATED_FULLY is a special case: if specified, it must be satisfied in addition to the other rules in the list.

The built-in rules are as follows:

IS_AUTHENTICATED_ANONYMOUSLY - anyone has access; no need for the user to log in

IS_AUTHENTICATED_REMEMBERED - only known users that have logged in or are remembered from a previous session are allowed access

IS_AUTHENTICATED_FULLY - users must log in to gain access, even if they checked “remember me” last time

The first two of these distinguish between known and unknown users, where known users are ones that have an entry in the ‘user’ database table. The last is typically applied in cases where the user is accessing particularly sensitive information, such as bank account or credit card data. After all, someone else could be accessing your application using the “remember me” cookie from the previous user.

You can also apply the annotation to the controller class itself, which results in all actions inheriting the rules defined by it. If an action has its own annotation, that overrides the class-level one. The annotation isn’t just limited to a list of rules like this either: take a look at the user guide to see how to use expressions to provide greater control over the rules.
Static URL rules

If annotations aren’t your thing, you can define access control rules via a static map in Config.groovy. If you like to keep your rules in one place, it’s ideal. Here is how you would define Hubbub’s rules using this mechanism:

import grails.plugins.springsecurity.SecurityConfigType

grails.plugins.springsecurity.securityConfigType = SecurityConfigType.InterceptUrlMap
grails.plugins.springsecurity.interceptUrlMap = [
‘/timeline’: [‘ROLE_USER’],
‘/person/*’: [‘IS_AUTHENTICATED_REMEMBERED’],
‘/post/followAjax’: [‘ROLE_USER’],
‘/post/addPostAjax’: [‘ROLE_USER’, ‘IS_AUTHENTICATED_FULLY’],
‘/**’: [‘IS_AUTHENTICATED_ANONYMOUSLY’]
]

Notice how the most general rule comes last? That’s because order is important: Spring Security iterates through the rules and applies the first one that matches the current URL. So if the ‘/**’ rule came first, your application would effectively be unprotected since all URLs would be matched to it. Also notice that you have to explicitly tell the plugin to use the map via the grails.plugins.springsecurity.securityConfigType settings.
Dynamic request maps

Do you want to update URL rules at runtime without restarting the application? If that’s the case, you’ll probably want to use request maps, which are basically URL rules stored in the database. To enable this mechanism, add the following to Config.groovy:

import grails.plugins.springsecurity.SecurityConfigType

grails.plugins.springsecurity.securityConfigType = SecurityConfigType.Requestmap

All you then have to do is create instances of the Requestmap domain class, for example in BootStrap.groovy:

new Requestmap(url: ‘/timeline’, configAttribute: ‘ROLE_USER’).save()
new Requestmap(url: ‘/person/*’, configAttribute: ‘IS_AUTHENTICATED_REMEMBERED’).save()
new Requestmap(url: ‘/post/followAjax’, configAttribute: ‘ROLE_USER’).save()
new Requestmap(url: ‘/post/addPostAjax’, configAttribute: ‘ROLE_USER,IS_AUTHENTICATED_FULLY’).save()
new Requestmap(url: ‘/**’, configAttribute: ‘IS_AUTHENTICATED_ANONYMOUSLY’).save()

Of course, there is a performance cost to this approach since it involves the database, but it is minimised through the use of caching. Take a look at the user guide for more information on this. Also, you don’t have to worry about the order of the rules in this case because the plugin picks the most specific URL pattern that matches the current URL.

Which of these approaches should you use? It depends on how your application is set up and how you think about access control. Annotations make sense where rules apply on a per-controller basis and controllers have distinct URLs. If you tend to group controllers under a single URL, like /admin/ or you simply like to keep all your rules in one place, then you’re probably better off with the static rules defined in Config.groovy. The third mechanism, request maps, only make sense if you want to add, change, or remove rules at runtime. A classic example where you might want to do this is in a CMS application, where URLs themselves are defined dynamically.

Whichever approach you take, once the rules are implemented your application is protected. For example, if you try to access the /timeline page in Hubbub at this point, you will be redirected to the standard login page:

Great! But who are you going to log in as? How are users going to log out? Protecting your pages is only the first step. You also need to make sure that you have the relevant security data (users and roles) and a user interface that’s security aware.
Next steps

With the access control in place, you need to look at the user experience. Do you really want users clicking on links that they don’t have access to? What about those roles you are using in the access control? When do they get created? Let’s answer those questions now.
Security data

Some applications only care whether a user is known or not and in such cases you don’t need to worry about roles because the IS_AUTHENTICATED_* rules are sufficient. But if your application needs more control over who has access to what, you will need roles. These are typically defined early in the life of the application and correspond to unchanging reference data. That makes BootStrap the ideal place to create them. For Hubbub, we add ‘user’ and ‘admin’ roles like so:

import org.example.SecRole

class BootStrap {
def init = {

def userRole = SecRole.findByAuthority(‘ROLE_USER’) ?: new SecRole(authority: ‘ROLE_USER’).save(failOnError: true)
def adminRole = SecRole.findByAuthority(‘ROLE_ADMIN’) ?: new SecRole(authority: ‘ROLE_ADMIN’).save(failOnError: true)

}
}

Of course, if the data already exists we don’t want to recreate it, hence why we use findByAuthority().

Adding users is almost as straightforward, but there are a couple of requirements that you need to bear in mind. First, the generated ‘user’ domain class has an enabled property that is false by default. If you don’t explicitly initialise it to true the corresponding user won’t be able to log in. Second, passwords are rarely stored in the database as plain text, so you will need to encode them first using an appropriate digest algorithm.

Fortunately, the plugin provides a useful service to help here: SpringSecurityService. Let’s say we want to create an ‘admin’ user in Hubbub’s BootStrap. The code would look something like this:

import org.example.*

class BootStrap {
def springSecurityService

def init = {
    ...
    def adminUser = SecUser.findByUsername('admin') ?: new SecUser(
            username: 'admin',
            password: springSecurityService.encodePassword('admin'),
            enabled: true).save(failOnError: true)

    if (!adminUser.authorities.contains(adminRole)) {
        SecUserSecRole.create adminUser, adminRole
    }
    ...
}

}

We simply inject the security service into BootStrap and then use its encodePassword() method to convert the plain text password to its hash. This approach works particularly well when you decide to change the digest algorithm you use, because the service will encode passwords using the same algorithm as the one used when comparing them for authentication. In other words, the above code stays the same no matter what algorithm is used.

Update As of version 1.2 of the Spring Security Core plugin, the generated User class automatically encodes the password when an instance is saved. Hence you no longer need to explicitly use SpringSecurityService.encodePassword()

Once the user is created, we check whether it has the ‘admin’ role and if it doesn’t, we assign the role to the user. We do this by way of the generated SecUserSecRole class and its create() method.

With the security data in place, and the knowledge of how to create it on demand where necessary, it’s time to make the user interface aware of authentication, users, and roles.
The user interface

There are two aspects of the UI I want to look at here: displaying information specific to the user and making sure that the user can only see what he’s allowed to. The first of these boils down to one question: how do we get the ‘user’ domain instance for the currently logged in user? Consider Hubbub’s timeline page, which displays all the posts of the people that the current user is following:

class PostController {
def springSecurityService

@Secured([‘ROLE_USER’])
def timeline = {
def user = SecUser.get(springSecurityService.principal.id)

    def posts = []
    if (user.following) {
        posts = Post.withCriteria {
            'in'("user", user.following)
            order("createdOn", "desc")
        }
    }
    [ posts: posts, postCount: posts.size() ]
}
...

}

As you can see, all we need to do is inject the security service again and use it to get hold of the principal. Unless you have created a custom version of the UserDetailsService (don’t worry if you haven’t come across this before), the principal will be an instance of org.codehaus.groovy.grails.plugins.springsecurity.GrailsUser whose id property contains the ID of the corresponding ‘user’ domain instance.

One thing you need to be aware of: if the current user is authenticated anonymously, i.e. he hasn’t logged in and isn’t remembered, the principal property will return a string instead. So if an action can be accessed by an unauthenticated user, make sure you check the type of the principal before using it!

What about ensuring users can only see what they are supposed to? For that, the plugin provides a rich set of GSP tags in the sec namespace. Let’s say we want to add a couple of navigation links to Hubbub, but we only want to display one of them when the user isn’t logged in and the other only if the user has the ROLE_USER role:

sec:ifNotLoggedIn
<g:link controller=“login” action=“auth”>Login</g:link>
</sec:ifNotLoggedIn>
<sec:ifAllGranted roles=“ROLE_USER”>
<g:link class=“create” controller=“post” action=“timeline”>My Timeline</g:link>
</sec:ifAllGranted>

The markup inside the sec:if* tags will only be rendered to the page if the condition is satisfied. The plugin provides several other similar tags that all behave in a consistent fashion. See the user guide for more information.

The above example also shows you how to create a link to the login page. Allowing the user to log out is similarly straightforward. Hubbub provides a side panel that displays amongst other things the name of the logged in user and a link to sign out:

<sec:username /> (<g:link controller=“logout”>sign out</g:link>)

Easy! The combination of these tags and the security service should be more than sufficient to integrate your user interface with Spring Security. Just remember to keep your user interface elements in sync with your access control rules: you don’t want bits of UI visible that result in an “unauthorised user” error.

I’ve now covered all the basic elements of the Spring Security plugin, but there are still two features that will affect a large number of users: AJAX requests and custom login forms.
The last pieces of the puzzle

How many web applications don’t use AJAX to some degree now? And how many really want to use the stock login form for their application? It’s fine for internal use, but I wouldn’t recommend it for anything that’s customer facing. Let’s start with AJAX.
Securing AJAX requests

Dynamic user interfaces based on AJAX bring a new set of problems to access control. It’s very easy to deal with a standard request that requires authentication: simply redirect the user to the login page and then redirect them back to the target page if the authentication is successful. But such a redirect doesn’t work well with AJAX. So what do you do?

The plugin gives you a way to deal with AJAX request differently to normal ones. When an AJAX request requires authentication, Spring Security redirects to the authAjax action in LoginController rather than auth. But wait, that’s still a redirect right? Yes, but you can implement the authAjax to send an error status or render JSON - basically anything that the client Javascript code can handle.

Unfortunately, the LoginController provided by the plugin doesn’t implement authAjax at this time, so you will have to do add it yourself:

import javax.servlet.http.HttpServletResponse

class LoginController {

def authAjax = {
response.sendError HttpServletResponse.SC_UNAUTHORIZED
}

}

This is a very simple implementation that returns a 401 HTTP status code. How do we deal with such a response? That depends on what you use to implement AJAX in the browser. The example Hubbub application uses adaptive AJAX tags, so I’ll use that to demonstrate the kind of thing you can do. This is part of the GSP template that is used for posting new messages:

<g:form action=“ajaxAdd”>
<g:textArea id=‘postContent’ name=“content” rows=“3” cols=“50” οnkeydοwn=“updateCounter()” />

<g:submitToRemote value=“Post”
url=“[controller: ‘post’, action: ‘addPostAjax’]”
update=“[success: ‘firstPost’]”
onSuccess=“clearPost(e)”
onLoading=“showSpinner(true)”
onComplete=“showSpinner(false)”
on401=“showLogin();”/>
</g:form>

As you can see, it has an on401 attribute that specifies a bit of Javascript that should be executed when the AJAX submission returns a 401 status code. That bit of Javascript can, for example, display a dynamic, client-side login form for the user to authenticate with. Hubbub uses the client-side code provided in the plugin’s user guide to do just that.

Note Version 1.1 of the plugin will come with a default implementation of the authAjax action.

You can also customise the ajaxSuccess and ajaxDenied actions to send back whatever response you want. As you can see, the server-side AJAX handling is simple and easy to customise. The real work has to be done in the client code.
Custom login forms

It’s no longer fashionable to dedicate an entire page to the login form. These days applications are more likely to have a content-rich home page with a discrete login form located somewhere on it, perhaps only made visible by some Javascript magic. It’s easy enough to provide your own dedicated login page (simply edit the auth action in LoginController and its associated GSP view to your heart’s content), but what about a login panel?

It’s not as hard as you might think. First of all, you need to decide where users should be redirected to when authentication is required. As you’ve probably gathered, this is /login/auth by default. Changing that default is as easy as adding a setting to Config.groovy:

grails.plugins.springsecurity.auth.loginFormUrl = ‘/’

This line tells the plugin to redirect to the home page whenever authentication is required. All you then need to do is add a login panel to the home page. Here’s an example GSP form that might go in such a panel:

Username:
Password:
try "glen" or "peter" with "password"

The key points here are:

the form must use the POST method;

the form must be submitted to <context>/j_spring_security_check;

the username field must have the name ‘j_username’;

the password field must have the name ‘j_password’; and

any “remember me” field must have the name ’_spring_security_remember_me’.

As long as these requirements are satisfied, the login form will work perfectly. Well, not quite perfectly. If an authentication attempt fails via your login form, you will find yourself redirected back to the old login page. Fortunately, this is quickly rectified by adding another configuration setting:

grails.plugins.springsecurity.failureHandler.defaultFailureUrl = ‘/’

And that’s all you need for a fully functioning login form! There are plenty of other options available to fine tune the behaviour, but you now have the basics on which to build.

This article has really only scratched the surface of the Spring Security plugin. I haven’t mentioned HTTP Basic and Digest Authentication, events, salted passwords and more. That doesn’t even include the other plugins that provide extra features such as alternative authentication mechanisms and access control lists (ACLs). But what you have read so far will enable you to get a fully working access control system up and running in no time. You will then be able to extend and customise as the need arises, knowing that Spring Security has more features than you will probably ever need.
comments powered by Disqus

translate:
翻译:

Spring Security是一个强大的库,用于保护您的应用程序,它提供了许多令人困惑的选项。基于Spring,它可以很容易地集成到Grails应用程序中。但为什么不省去麻烦,使用新的改进Grails插件呢?
这个插件经历了几个从Acegi插件开始的进化阶段。它的最新版本是对Spring Security 3和spring3的完全重写。这样做的一个结果是,该插件只适用于Grails 1.2.2及更高版本。另一个重要的变化是不再只有一个Spring安全插件:一些特性已经被分解为可选插件。因此,现在您只需在应用程序中包含所需的功能。
那么插件给了你什么呢?核心提供了基于用户和角色的易用包中的访问控制所必需的基础知识。事实上,除了核心插件,许多应用程序不需要其他插件。
Spring Security 是一个强大的安全框架,用于保护 Java 应用程序。它提供了一系列功能来帮助开发人员实现认证和授权,确保应用程序的安全性。以下是 Spring Security 的一些关键特点:

  1. 认证(Authentication):Spring Security 提供了多种认证机制,如用户名密码、OAuth2、JWT 等。通过配置不同的认证方式,可以满足不同场景下的安全需求。

  2. 授权(Authorization):Spring Security 支持基于角色和权限的访问控制。开发人员可以通过配置文件或注解来定义用户的角色和权限,从而实现细粒度的访问控制。

  3. 会话管理(Session Management):Spring Security 提供了会话管理功能,可以对用户的会话进行创建、销毁和验证。此外,还可以配置会话超时时间,以防止长时间未操作的用户保持登录状态。

  4. CSRF 防护(CSRF Protection):Spring Security 内置了跨站请求伪造(CSRF)防护机制,可以有效防止恶意网站通过伪造请求攻击用户的账户。

  5. 加密和解密(Encryption and Decryption):Spring Security 支持对敏感数据进行加密和解密,以确保数据在传输过程中的安全性。

  6. 集成其他安全技术:Spring Security 可以轻松与其他安全技术集成,如 SSL/TLS、LDAP、Active Directory 等,以满足企业级应用的安全需求。

  7. 易于扩展和定制:Spring Security 采用了模块化设计,开发人员可以根据需要添加或替换某些组件,以实现定制化的安全解决方案。

总之,Spring Security 是一个功能强大且灵活的安全框架,可以帮助开发人员轻松地为 Java 应用程序添加各种安全功能。通过合理配置和使用 Spring Security,可以大大提高应用程序的安全性。

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值