描述:
本文档将实现单用户登录,实际效果是:当一个用户在一个地方登录了之后,另一个地方也用该用户登录,前一个登录被迫下线,每次登录都会用新的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,相互交流学习!