Grails3+spring security实现单用户登录

描述:

本文档将实现单用户登录,实际效果是:当一个用户在一个地方登录了之后,另一个地方也用该用户登录,前一个登录被迫下线,每次登录都会用新的session替换就的session。

1、新建项目目录结构如图所示

2、打开根目录下的build.gradle文件,dependencies中添加spring-security依赖

compile 'org.grails.plugins:spring-security-core:3.1.2'

3、创建用户、角色的domain

3.1用户(UserInfo)

packagecom.system

importgroovy.transform.EqualsAndHashCode

importgroovy.transform.ToString

@EqualsAndHashCode(includes='username')

@ToString(includes='username', includeNames=true, includePackage=false)

classUserInfoimplementsSerializable {

transientspringSecurityService

private static final longserialVersionUID=1

Stringusername

Stringpassword

booleanenabled=true

booleanaccountExpired

booleanaccountLocked

booleanpasswordExpired

Stringnickname

SetgetAuthorities() {

(UserRole.findAllByUser(this)asList)*.roleasSet

}

staticconstraints= {

passwordblank:false,password:true

usernameblank:false,unique:true

nicknamenullable:true,maxSize:15

}

staticmapping= {

passwordcolumn:'`password`'

}

defbeforeInsert() {

encodePassword()

}

defbeforeUpdate() {

if(isDirty('password')) {

encodePassword()

}

}

protected voidencodePassword() {

password=springSecurityService.encodePassword(password)

}

}

3.2 RoleInfo(角色)

packagecom.system

importgroovy.transform.EqualsAndHashCode

importgroovy.transform.ToString

@EqualsAndHashCode(includes='authority')

@ToString(includes='authority', includeNames=true, includePackage=false)

classRoleInfoimplementsSerializable {

private static final longserialVersionUID=1

Stringauthority

Stringremark

staticconstraints= {

authorityblank:false,unique:true

remarkblank:false

}

staticmapping= {

cachetrue

}

}

3.3用户-角色关联(UserRole)

packagecom.system

importgrails.gorm.DetachedCriteria

importgroovy.transform.ToString

importorg.codehaus.groovy.util.HashCodeHelper

@ToString(cache=true, includeNames=true, includePackage=false)

classUserRoleimplementsSerializable {

private static final longserialVersionUID=1

UserInfouser

RoleInforole

@Override

booleanequals(other) {

if(otherinstanceofUserRole) {

other.userId==user?.id&& other.roleId==role?.id

}

}

@Override

inthashCode() {

inthashCode = HashCodeHelper.initHash()

if(user) {

hashCode = HashCodeHelper.updateHash(hashCode,user.id)

}

if(role) {

hashCode = HashCodeHelper.updateHash(hashCode,role.id)

}

hashCode

}

staticUserRoleget(longuserId,longroleId) {

criteriaFor(userId, roleId).get()

}

static booleanexists(longuserId,longroleId) {

criteriaFor(userId, roleId).count()

}

private staticDetachedCriteria criteriaFor(longuserId,longroleId) {

UserRole.where{

user== UserInfo.load(userId) &&

role== RoleInfo.load(roleId)

}

}

staticUserRolecreate(UserInfo user, RoleInfo role,booleanflush =false) {

definstance =newUserRole(user: user,role: role)

instance.save(flush: flush)

instance

}

static booleanremove(UserInfo u, RoleInfo r) {

if(u !=null&& r !=null) {

UserRole.where{user== u &&role== r }.deleteAll()

}

}

static intremoveAll(UserInfo u) {

u ==null?0:UserRole.where{user== u }.deleteAll()as int

}

static intremoveAll(RoleInfo r) {

r ==null?0:UserRole.where{role== r }.deleteAll()as int

}

staticconstraints= {

rolevalidator: { RoleInfo r,UserRoleur ->

if(ur.user?.id) {

UserRole.withNewSession{

if(UserRole.exists(ur.user.id, r.id)) {

return['userRole.exists']

}

}

}

}

}

staticmapping= {

idcomposite: ['user','role']

versionfalse

}

}

4、创建登录控制器LoginController

packagecom.system

importgrails.converters.JSON

importgrails.plugin.springsecurity.SpringSecurityUtils

importorg.springframework.context.MessageSource

importorg.springframework.security.access.annotation.Secured

importorg.springframework.security.authentication.AccountExpiredException

importorg.springframework.security.authentication.AuthenticationTrustResolver

importorg.springframework.security.authentication.CredentialsExpiredException

importorg.springframework.security.authentication.DisabledException

importorg.springframework.security.authentication.LockedException

importorg.springframework.security.core.Authentication

importorg.springframework.security.core.context.SecurityContextHolder

importorg.springframework.security.web.WebAttributes

importjavax.servlet.http.HttpServletResponse

@Secured('permitAll')

classLoginController {

/**依赖注入认证接口authenticationTrustResolver. */AuthenticationTrustResolverauthenticationTrustResolver

/**依赖注入springSecurityService. */defspringSecurityService

/**依赖注入messageSource. */MessageSourcemessageSource

/**若登录成功,直接跳转到首页,否则跳转到auth页面登录*/defindex() {

if(springSecurityService.isLoggedIn()) {

redirecturi:conf.successHandler.defaultTargetUrl

}

else{

redirectaction:'auth',params:params

}

}

/**登录页面*/defauth() {

defconf =getConf()

if(springSecurityService.isLoggedIn()) {

redirecturi: conf.successHandler.defaultTargetUrl

return

}

String postUrl =request.contextPath+ conf.apf.filterProcessesUrl

renderview:'auth',model: [postUrl: postUrl,

rememberMeParameter: conf.rememberMe.parameter,

usernameParameter: conf.apf.usernameParameter,

passwordParameter: conf.apf.passwordParameter,

gspLayout: conf.gsp.layoutAuth]

}

/** The redirect action for Ajax requests. */defauthAjax() {

response.setHeader'Location',conf.auth.ajaxLoginFormUrl

render(status: HttpServletResponse.SC_UNAUTHORIZED,text:'Unauthorized')

}

/**普通请求拒绝访问*/defdenied() {

if(springSecurityService.isLoggedIn() &&authenticationTrustResolver.isRememberMe(authentication)) {

// have cookie but the page is guarded with IS_AUTHENTICATED_FULLY (or the equivalent expression)

redirectaction:'full',params:params

return

}

[gspLayout:conf.gsp.layoutDenied]

}

/** Login page for users with a remember-me cookie but accessing a IS_AUTHENTICATED_FULLY page. */deffull() {

defconf =getConf()

renderview:'auth',params:params,

model: [hasCookie:authenticationTrustResolver.isRememberMe(authentication),

postUrl:request.contextPath+ conf.apf.filterProcessesUrl,

rememberMeParameter: conf.rememberMe.parameter,

usernameParameter: conf.apf.usernameParameter,

passwordParameter: conf.apf.passwordParameter,

gspLayout: conf.gsp.layoutAuth]

}

/** ajax登录认证失败信息提示*/defauthfail() {

String msg =''

defexception =session[WebAttributes.AUTHENTICATION_EXCEPTION]

if(exception) {

if(exceptioninstanceofAccountExpiredException) {

msg =messageSource.getMessage('springSecurity.errors.login.expired',null,"Account Expired",request.locale)

}

else if(exceptioninstanceofCredentialsExpiredException) {

msg =messageSource.getMessage('springSecurity.errors.login.passwordExpired',null,"Password Expired",request.locale)

}

else if(exceptioninstanceofDisabledException) {

msg =messageSource.getMessage('springSecurity.errors.login.disabled',null,"Account Disabled",request.locale)

}

else if(exceptioninstanceofLockedException) {

msg =messageSource.getMessage('springSecurity.errors.login.locked',null,"Account Locked",request.locale)

}

else{

msg =messageSource.getMessage('springSecurity.errors.login.fail',null,"Authentication Failure",request.locale)

}

}

if(springSecurityService.isAjax(request)) {

render([error: msg]asJSON)

}

else{

flash.message= msg

redirectaction:'auth',params:params

}

}

/** ajax登录成功*/defajaxSuccess() {

render([success:true,username:authentication.name]asJSON)

}

/** ajaax拒绝访问*/defajaxDenied() {

render([error:'access denied']asJSON)

}

protectedAuthenticationgetAuthentication() {

SecurityContextHolder.context?.authentication

}

protectedConfigObjectgetConf() {

SpringSecurityUtils.securityConfig}

/**单用户登录(已登录返回给用户提示)*/defalready() {

renderview:"already"

}

}

5、创建注销控制器LogoutController

packagecom.system

importgrails.plugin.springsecurity.SpringSecurityUtils

importorg.springframework.security.access.annotation.Secured

importorg.springframework.security.web.RedirectStrategy

@Secured('permitAll')

classLogoutController {

/**依赖注入RedirectStrategy. */RedirectStrategyredirectStrategy

/***注销方法*/defindex() {

//        if (!request.post && SpringSecurityUtils.getSecurityConfig().logout.postOnly) {

//            response.sendError HttpServletResponse.SC_METHOD_NOT_ALLOWED // 405

//            return

//        }

//TODO put any pre-logout code hereredirectStrategy.sendRedirectrequest,response, SpringSecurityUtils.securityConfig.logout.filterProcessesUrl// '/logoff'

response.flushBuffer()

}

}

6、自定义一个ConcurrentSingleSessionAuthenticationStrategy类实现SessionAuthenticationStrategy接口覆盖默认方法

packagecom.session

importorg.springframework.security.core.Authentication

importorg.springframework.security.core.session.SessionRegistry

importorg.springframework.security.web.authentication.session.SessionAuthenticationStrategy

importorg.springframework.util.Assert

importjavax.servlet.http.HttpServletRequest

importjavax.servlet.http.HttpServletResponse

/***会话管理类*/classConcurrentSingleSessionAuthenticationStrategyimplementsSessionAuthenticationStrategy {

privateSessionRegistrysessionRegistry

/***@param将新的会话赋值给sessionRegistry*/publicConcurrentSingleSessionAuthenticationStrategy(SessionRegistry sessionRegistry) {

Assert.notNull(sessionRegistry,"SessionRegistry cannot be null")

this.sessionRegistry= sessionRegistry

}

/***覆盖父类的onAuthentication方法*用新的session替换就的session*/public voidonAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {

defsessions =sessionRegistry.getAllSessions(authentication.getPrincipal(),false)

defprincipals =sessionRegistry.getAllPrincipals()

sessions.each {

if(it.principal== authentication.getPrincipal()) {

it.expireNow()

}

}

}

}

(注:此类我是在src/main/groovy里面创建的,你也可以在其他地方创建)

7、打开grails-app/conf/spring/resource.groovy,配置DSL

7.1配置

importcom.session.ConcurrentSingleSessionAuthenticationStrategy

importorg.springframework.security.core.session.SessionRegistryImpl

importorg.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy

importorg.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy

importorg.springframework.security.web.authentication.session.SessionFixationProtectionStrategy

importorg.springframework.security.web.session.ConcurrentSessionFilter

// Place your Spring DSL code here

beans = {

sessionRegistry(SessionRegistryImpl)

//很重要

sessionFixationProtectionStrategy(SessionFixationProtectionStrategy){

migrateSessionAttributes=true

alwaysCreateSession=true

}

// "/login/already"为重定向请求

concurrentSingleSessionAuthenticationStrategy(ConcurrentSingleSessionAuthenticationStrategy,ref('sessionRegistry'))

registerSessionAuthenticationStrategy(RegisterSessionAuthenticationStrategy,ref('sessionRegistry'))

sessionAuthenticationStrategy(CompositeSessionAuthenticationStrategy,[ref('concurrentSingleSessionAuthenticationStrategy'), ref('sessionFixationProtectionStrategy'), ref('registerSessionAuthenticationStrategy')])

concurrentSessionFilter(ConcurrentSessionFilter, ref('sessionRegistry'),"/login/already")

}

8、在grails-app/conf目录下创建application.groovy类

8.1配置

grails.plugin.springsecurity.userLookup.usernamePropertyName ="username"

grails.plugin.springsecurity.userLookup.passwordPropertyName ="password"

grails.plugin.springsecurity.authority.className="com.system.RoleInfo"

grails.plugin.springsecurity.userLookup.userDomainClassName="com.system.UserInfo"

grails.plugin.springsecurity.userLookup.authorityJoinClassName="com.system.UserRole"

grails.plugin.springsecurity.controllerAnnotations.staticRules = [

[pattern:'/',access: ['permitAll']],

[pattern:'/error',access: ['permitAll']],

[pattern:'/index',access: ['permitAll']],

[pattern:'/index.gsp',access: ['permitAll']],

[pattern:'/shutdown',access: ['permitAll']],

[pattern:'/assets/**',access: ['permitAll']],

[pattern:'/**/js/**',access: ['permitAll']],

[pattern:'/**/css/**',access: ['permitAll']],

[pattern:'/**/images/**',access: ['permitAll']],

[pattern:'/**/favicon.ico',access: ['permitAll']],

[pattern:'/login/already.gsp',access: ['permitAll']],

[pattern:'/user/**',access:'ROLE_USER'],

[pattern:'/admin/**',access: ['ROLE_ADMIN','isFullyAuthenticated()']]

]

grails.plugin.springsecurity.interceptUrlMap= [

[pattern:'/',access: ['permitAll']],

[pattern:'/error',access: ['permitAll']],

[pattern:'/index',access: ['permitAll']],

[pattern:'/index.gsp',access: ['permitAll']],

[pattern:'/shutdown',access: ['permitAll']],

[pattern:'/assets/**',access: ['permitAll']],

[pattern:'/**/js/**',access: ['permitAll']],

[pattern:'/**/css/**',access: ['permitAll']],

[pattern:'/**/images/**',access: ['permitAll']],

[pattern:'/**/favicon.ico',access: ['permitAll']],

[pattern:'/login/**',access: ['permitAll']],

[pattern:'/login/already',access: ['permitAll']],

[pattern:'/logout/**',access: ['permitAll']]

]

grails.plugin.springsecurity.filterChain.filterNames = ['securityContextPersistenceFilter','logoutFilter','concurrentSessionFilter','rememberMeAuthenticationFilter','anonymousAuthenticationFilter','exceptionTranslationFilter','filterInvocationInterceptor']

9、打开grails-app/init/BootStrap.groovy

9.1保存用户、角色、用户-角色信息

importcom.system.RoleInfo

importcom.system.UserInfo

importcom.system.UserRole

classBootStrap{

definit= { servletContext ->

//创建角色

defrole1 =newRoleInfo(authority:"ROLE_ADMIN",remark:"管理员").save()

defrole2 =newRoleInfo(authority:"ROLE_SUPSYS",remark:"超级管理员").save()

defrole3 =newRoleInfo(authority:"ROLE_USER",remark:"普通用户").save()

//创建用户

defuser1 =newUserInfo(username:"admin",password:"admin").save()

defuser2 =newUserInfo(username:"super",password:"super").save()

defuser3 =newUserInfo(username:"user",password:"user").save()

//用户角色关联

UserRole.createuser1, role1,true

UserRole.createuser2, role2,true

UserRole.createuser3, role3,true

}

defdestroy= {

}

}

最后到这里就完成了,可以启动项目进行测试了,需要说明的是,在此过程中没有设计到gsp页面的代码,同学们自己写吧。文档可能有语意不明的地方,还望各位同学多多包涵。有不清楚的Q我:342418262,相互交流学习!

你可能感兴趣的:(Grails3+spring security实现单用户登录)