描述:
本文檔將實現(xiàn)單用戶登錄,實際效果是:當一個用戶在一個地方登錄了之后,另一個地方也用該用戶登錄,前一個登錄被迫下線,每次登錄都會用新的session替換就的session。
1、新建項目目錄結構如圖所示
2、打開根目錄下的build.gradle文件,dependencies中添加spring-security依賴
compile 'org.grails.plugins:spring-security-core:3.1.2'
3、創(chuàng)建用戶、角色的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用戶-角色關聯(lián)(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、創(chuàng)建登錄控制器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、創(chuàng)建注銷控制器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類實現(xiàn)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里面創(chuàng)建的,你也可以在其他地方創(chuàng)建)
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目錄下創(chuàng)建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 ->
//創(chuàng)建角色
defrole1 =newRoleInfo(authority:"ROLE_ADMIN",remark:"管理員").save()
defrole2 =newRoleInfo(authority:"ROLE_SUPSYS",remark:"超級管理員").save()
defrole3 =newRoleInfo(authority:"ROLE_USER",remark:"普通用戶").save()
//創(chuàng)建用戶
defuser1 =newUserInfo(username:"admin",password:"admin").save()
defuser2 =newUserInfo(username:"super",password:"super").save()
defuser3 =newUserInfo(username:"user",password:"user").save()
//用戶角色關聯(lián)
UserRole.createuser1, role1,true
UserRole.createuser2, role2,true
UserRole.createuser3, role3,true
}
defdestroy= {
}
}
最后到這里就完成了,可以啟動項目進行測試了,需要說明的是,在此過程中沒有設計到gsp頁面的代碼,同學們自己寫吧。文檔可能有語意不明的地方,還望各位同學多多包涵。有不清楚的Q我:342418262,相互交流學習!