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,相互交流学习!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容