安庆大理运城常德铜陵江西
投稿投诉
江西南阳
嘉兴昆明
铜陵滨州
广东西昌
常德梅州
兰州阳江
运城金华
广西萍乡
大理重庆
诸暨泉州
安庆南充
武汉辽宁

SpringSecurity接口认证鉴权入门实践指南

1月21日 阴阳狱投稿
  前言
  WebAPI接口服务场景里,用户的认证和鉴权是很常见的需求,SpringSecurity据说是这个领域里事实上的标准,实践下来整体设计上确实有不少可圈可点之处,也在一定程度上印证了小伙们经常提到的太复杂了的说法也是很有道理的。
  本文以一个简单的SpringBootWeb以应用为例,重点介绍以下内容:演示SpringSecurity接口认证和鉴权的配置方法;以内存和数据库为例,介绍认证和鉴权数据的存储和读取机制;若干模块的自定义实现,包括:认证过滤器、认证或鉴权失败处理器等。SpringBoot示例
  创建SpringBoot示例,用于演示SpringSecurity在SpringBoot环境下的应用,简要介绍四部分内容:pom。xml、application。yml、IndexController和HelloController。SpringBootpom。xml。。。bootexampleartifactIddependenciesdependencygroupIdorg。springframework。bootgroupIdspringbootstarterwebartifactIddependencydependencygroupIdorg。springframework。bootgroupIdspringbootstarterloggingartifactIddependencydependencies
  bootexample是用于演示的SpringBoot项目子模块(Module)。
  注:依赖项的版本已在项目pom。xmldependencyManagement中声明。SpringBootapplication。ymlspring:application:name:exampleserver:port:9999logging:level:root:info
  SpringBoot应用名称为example,实例端口为9999。SpringBootIndexControllerRestControllerRequestMapping()publicclassIndexController{GetMappingpublicStringindex(){}}
  IndexController实现一个接口:。SpringBootHelloControllerRestControllerRequestMapping(hello)publicclassHelloController{GetMapping(world)publicStringworld(){}GetMapping(name)publicStringname(){}}
  HelloController实现两个接口:helloworld和helloname。
  编译启动SpringBoot应用,通过浏览器请求接口,请求路径和响应结果:http:localhost:9999indexhttp:localhost:9999helloworldhelloworldhttp:localhost:9999hellonamehelloname
  SpringBoot示例准备完成。SpringBoot集成SpringSecurity
  SpringBoot集成SpringSecurity仅需要在pom。xml中添加相应的依赖:springbootstartersecurity,如下:dependencies。。。dependencygroupIdorg。springframework。bootgroupIdspringbootstartersecurityartifactIddependencydependencies
  编译启动应用,相对于普通的SpringBoot应用,我们可以在命令行终端看到特别的两行日志:2022010916:05:57。437INFO87581〔main〕。s。s。UserDetailsServiceAutoConfiguration:Usinggeneratedsecuritypassword:3ef27867e9384fa4b5da5015f0deab7b2022010916:05:57。525INFO87581〔main〕o。s。s。web。DefaultSecurityFilterChain:Willsecureanyrequestwith〔org。springframework。security。web。context。request。async。WebAsyncManagerIntegrationFilter11e355ca,org。springframework。security。web。context。SecurityContextPersistenceFilter5114b7c7,org。springframework。security。web。header。HeaderWriterFilter24534cb0,org。springframework。security。web。csrf。CsrfFilter77c233af,org。springframework。security。web。authentication。logout。LogoutFilter5853ca50,org。springframework。security。web。authentication。UsernamePasswordAuthenticationFilter6d074b14,org。springframework。security。web。authentication。ui。DefaultLoginPageGeneratingFilter3206174f,org。springframework。security。web。authentication。ui。DefaultLogoutPageGeneratingFilter70d63e05,org。springframework。security。web。authentication。www。BasicAuthenticationFilter5115f590,org。springframework。security。web。savedrequest。RequestCacheAwareFilter767f6ee7,org。springframework。security。web。servletapi。SecurityContextHolderAwareRequestFilter7b6c6e70,org。springframework。security。web。authentication。AnonymousAuthenticationFiltere11ecfa,org。springframework。security。web。session。SessionManagementFilter106d77da,org。springframework。security。web。access。ExceptionTranslationFilter7b66322e,org。springframework。security。web。access。intercept。FilterSecurityInterceptor3e5fd2b1〕
  表示SpringSecurity已在SpringBoot应用中生效。默认情况下,SpringSecurity自动化地帮助我们完成以下三件事件:开启FormLogin登录认证模式;我们使用浏览器请求接口:http:localhost:9999会发现请求会被重定向至页面login:http:localhost:9999login提示使用用户名和密码登录:生成用于登录的用户名和密码;用户名为user,密码会输出到应用的启动日志:Usinggeneratedsecuritypassword:3ef27867e9384fa4b5da5015f0deab7b每一次应用启动,密码都会重新随机生成。注册用于认证和鉴权的过滤器;SpringSecurity本质就是通过过滤器或过滤器(链)实现的,每一个接口请求都会按顺序经过这些过滤器的过滤,每个过滤器承担的各自的职责,组合起来共同完成认证和鉴权。根据配置的不同,注册的过滤器也会有所不同,默认情况下,加载的过滤器列表可以参考启动日志:WebAsyncManagerIntegrationFilterSecurityContextPersistenceFilterHeaderWriterFilterCsrfFilterLogoutFilterUsernamePasswordAuthenticationFilterDefaultLoginPageGeneratingFilterDefaultLogoutPageGeneratingFilterBasicAuthenticationFilterRequestCacheAwareFilterSecurityContextHolderAwareRequestFilterAnonymousAuthenticationFilterSessionManagementFilterExceptionTranslationFilterFilterSecurityInterceptor
  使用SpringSecurity默认为我们生成的用户名和密码进行登录(Signin),成功之后会自动重定向至:index
  之后我们就可以通过浏览器正常请求helloworld和helloname。
  默认情况下,SpringSecurity仅支持基于FormLogin方式的认证,只能使用固定的用户名和随机生成的密码,且不支持鉴权。如果想要使用更丰富的安全特性:其他认证方式,如:HttpBasic自定义用户名和密码鉴权
  则需要我们自定义配置SpringSecurity。自定义配置可以通过两种方式实现:JavaConfiguration:使用Java代码的方式配置SecurityNameSpaceConfiguration:使用XML文件的方式配置
  本文以JavaConfiguration的方式为例进行介绍,需要我们提供一个继承自WebSecurityConfigurerAdapter配置类,然后通过重写若干方法进而实现自定义配置。importorg。springframework。context。annotation。Cimportorg。springframework。security。config。annotation。web。builders。HttpSimportorg。springframework。security。config。annotation。web。configuration。WebSecurityConfigurerAConfigurationpublicclassSecurityConfigextendsWebSecurityConfigurerAdapter{Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{}}
  SecurityConfig使用Configuration注解(配置类),继承自WebSecurityConfigurerAdapter,本文通过重写configure方法实现自定义配置。
  需要注意:WebSecurityConfigurerAdapter中有多个名称为configure重载方法,这里使用的是参数类型为HttpSecurity的方法。
  注:SpringSecurity默认自动化配置参考SpringBootAutoConfiguration。SpringSecurity使用HttpBasic认证protectedvoidconfigure(HttpSecurityhttp)throwsException{http。authorizeHttpRequests(authorizeauthorize。anyRequest()。authenticated())。httpBasic();}http。authorizeHttpRequests()
  用以指定哪些请求需要什么样的认证或授权,这里使用anyRequest()和authenticated()表示所有的请求均需要认证。http。authorizeHttpRequests()
  表示我们使用HttpBasic认证。
  编译启动应用,会发现终端仍会输出密码:Usinggeneratedsecuritypassword:e2c774678c464fe1ab32eb87558b8c0e
  因为,我们仅仅改变的是认证方式。
  为方便演示,我们使用CURL直接请求接口:curlhttp:localhost:9999{timestamp:20220110T02:47:20。82000:00,status:401,error:Unauthorized,path:}
  会提示我们Unauthorized,即:没有认证。
  我们按照HttpBasic要求添加请求头部参数Authorization,它的值:BasicBase64(user:e2c774678c464fe1ab32eb87558b8c0e)
  即:BasicdXNlcjplMmM3NzQ2Ny04YzQ2LTRmZTEtYWIzMi1lYjg3NTU4YjhjMGU
  再次请求接口:curlHAuthorization:BasicdXNlcjplMmM3NzQ2Ny04YzQ2LTRmZTEtYWIzMi1lYjg3NTU4YjhjMGUhttp:localhost:9999index
  认证成功,接口正常响应。SpringSecurity自定义用户名和密码
  使用默认用户名和随机密码的方式不够灵活,大部分场景都需要我们支持多个用户,且分别为他们设置相应的密码,这就涉及到两个问题:用户名和密码如何读取(查询)用户名和密码如何存储(增加删除修改)
  对于读取,SpringSecurity设计了UserDetailsService接口:publicinterfaceUserDetailsService{UserDetailsloadUserByUsername(Stringusername)throwsUsernameNotFoundE}loadUserByUsername
  实现按照用户名(username)从某个存储介质中加载相对应的用户信息(UserDetails)。username
  用户名,客户端发送请求时写入的用于用户名。UserDetails
  用户信息,包括用户名、密码、权限等相关信息。
  注意:用户信息不只用户名和用户密码。
  对于存储,SpringSecurity设计了UserDetailsManager接口:publicinterfaceUserDetailsManagerextendsUserDetailsService{voidcreateUser(UserDetailsuser);voidupdateUser(UserDetailsuser);voiddeleteUser(Stringusername);voidchangePassword(StringoldPassword,StringnewPassword);booleanuserExists(Stringusername);}createUser
  创建用户信息updateUser
  修改用户信息deleteUser
  删除用户信息changePassword
  修改当前用户的密码userExists
  检查用户是否存在
  注意:UserDetailsManager继承自UserDetailsService。
  也就是说,我们可以通过提供一个已实现接口UserDetailsManager的类,并重写其中的若干方法,基于某种存储介质,定义用户名、密码等信息的存储和读取逻辑;然后将这个类的实例以Bean的形式注入SpringSecurity,就可以实现用户名和密码的自定义。
  实际上,SpringSecurity仅关心如何读取,存储可以由业务系统自行实现;相当于,只实现接口UserDetailsService即可。
  SpringSecurity已经为我们预置了两种常见的存储介质实现:InMemoryUserDetailsManager,基于内存的实现JdbcUserDetailsManager,基于数据库的实现
  InMemoryUserDetailsManager和JdbcUserDetailsManager均实现接口UserDetailsManager,本质就是对于UserDetails的CRUD。我们先介绍UserDetails,然后再分别介绍基于内存和数据库的实现。UserDetails
  UserDetails是用户信息的抽象接口:publicinterfaceUserDetailsextendsSerializable{C?extendsGrantedAuthoritygetAuthorities();StringgetPassword();StringgetUsername();booleanisAccountNonExpired();booleanisAccountNonLocked();booleanisCredentialsNonExpired();booleanisEnabled();}getUsername
  获取用户名。getPassword
  获取密码。getAuthorities
  获取权限,可以简单理解为角色名称(字符串),用于实现接口基于角色的授权访问,详情见后文。其他
  获取用户是否可用,或用户密码是否过期或锁定。
  SpringSecurity提供了一个UserDetails的实现类User,用于用户信息的实例表示。另外,User提供Builder模式的对象构建方式。UserDetailsuserUser。builder()。username(user)。password({bcrypt}2a10GRLdNijSQMUvlau9ofL。eDwmoohzzS7。rmNSJZ。0FxOBTk76klW)。roles(USER)。build();username
  设置用户名称。password
  设置密码,SpringSecurity不建议使用明文字符串存储密码,密码格式:{id}encodedPassword
  其中,id为加密算法标识,encodedPassword为密码加密后的字符串。这里以加密算法bcrypt为例,详细内容可参考PasswordStorage。roles
  设置角色,支持多个。
  UserDetails实例创建完成之后,就可以使用UserDetailsManager的具体实现进行存储和读取。InMemory
  InMemoryUserDetailsManager是SpringSecurity为我们提供的基于内存实现的UserDetailsManager。ConfigurationpublicclassSecurityConfigextendsWebSecurityConfigurerAdapter{。。。BeanpublicUserDetailsManagerusers(){UserDetailsuserUser。builder()。username(userA)。password({bcrypt}2a10CrPsv1X3hM。giwVZyNsrKuaRvpJZyGQycJg78xT7Dm68K4DWNlxS)。roles(USER)。build();InMemoryUserDetailsManagermanagernewInMemoryUserDetailsManager();manager。createUser(user);}}创建用户信息实例user,用户名为userA,密码为123456(使用Bcrypt算法加密);认证并需要角色参与,但roles必须被设置,这里指定为USER;创建InMemoryUserDetailsManager实例使用createUser方法将user存储至相当于把用户信息存储至内存介质中;返回
  使用Bean将InMemoryUserDetailsManager实例注入SpringSecurity。
  创建InMemoryUserDetailsManager实例之后,并不是必须立即调用createUser添加用户信息,也可以在业务系统的其它地方获取已注入的InMemoryUserDetailsManager动态存储UserDetails实例。
  编译启动应用,使用我们自己创建的用户名和密码(userA123456)访问接口:curlHAuthorization:BasicdXNlckE6MTIzNDU2http:localhost:9999index
  基于内存介质自定义的用户名和密码已生效,接口正常响应。JDBC
  JdbcUserDetailsManager是SpringSecurity为我们提供的基于数据库实现的UserDetailsManager,相较于InMemoryUserDetailsManager使用略复杂,需要我们创建数据表,并准备好数据库连接需要的数据源(DataSource),JdbcUserDetailsManager实例的创建依赖于数据源。
  JdbcUserDetailsManager可以与业务系统共用一个数据库数据源实例,本文不讨论数据源的相关配置。
  以MySQL为例,创建数据表语句:createtableusers(usernamevarchar(50)notnullprimarykey,passwordvarchar(500)notnull,enabledbooleannotnull);createtableauthorities(usernamevarchar(50)notnull,authorityvarchar(50)notnull,constraintfkauthoritiesusersforeignkey(username)referencesusers(username));createuniqueindexixauthusernameonauthorities(username,authority);
  其他数据库语句可参考UserSchema。
  JdbcUserDetailsManager实例的创建与注入,除获取已注入的数据源实例dataS创建实例时需要传入数据源实例dataS
  之外,整体流程与InMemoryUserDetailsManager类似,不再赘述。ConfigurationpublicclassSecurityConfigextendsWebSecurityConfigurerAdapter{。。。。。。AutowiredprivateDataSourcedataSBeanpublicUserDetailsManagerusers(){UserDetailsuserUser。builder()。username(user)。password({bcrypt}2a10CrPsv1X3hM。giwVZyNsrKuaRvpJZyGQycJg78xT7Dm68K4DWNlxS)。roles(USER)。build();JdbcUserDetailsManagermanagernewJdbcUserDetailsManager(dataSource);manager。createUser(user);}}
  在业务系统中获取已注入的JdbcUserDetailsManager实例,可以动态存储UserDetails实例。
  编译启动应用,使用我们自己创建的用户名和密码(userA123456)访问接口:curlHAuthorization:BasicdXNlckE6MTIzNDU2http:localhost:9999index
  基于数据库介质自定义的用户名和密码已生效,接口正常响应。SpringSecurity鉴权
  SpringSecurity可以提供基于角色的权限控制:不同的用户可以属于不同的角色不同的角色可以访问不同的接口
  假设,存在两个角色USER(普通用户)和ADMIN(管理员),
  角色USER可以访问接口helloname,
  角色ADMIN可以访问接口helloworld,
  所有用户认证后可以访问接口。
  我们需要按上述需求重新设置HttpSecurity:protectedvoidconfigure(HttpSecurityhttp)throwsException{http。authorizeHttpRequests(authorizeauthorize。mvcMatchers(helloname)。hasRole(USER)。mvcMatchers(helloworld)。hasRole(ADMIN)。anyRequest()。authenticated())。httpBasic();}mvcMatchers(helloname)。hasRole(USER)
  设置角色USER可以访问接口helloname。mvcMatchers(helloworld)。hasRole(ADMIN)
  设置角色ADMIN可以访问接口helloworld。anyRequest()。authenticated()
  设置其他接口认证后即可访问。
  mvcMatchers支持使用通配符。
  创建属于角色USER和ADMIN的用户:
  用户名:userA,密码:123456,角色:USER
  用户名:userB,密码:abcdef,角色:ADMINBeanpublicUserDetailsManagerusers(){UserDetailsuserAUser。builder()。username(userA)。password({bcrypt}2a10CrPsv1X3hM。giwVZyNsrKuaRvpJZyGQycJg78xT7Dm68K4DWNlxS)。roles(USER)。build();UserDetailsuserBUser。builder()。username(userB)。password({bcrypt}2a10PES8fUdtRrQ9OxLqf4CofOfcXBLQ3lkY2TSIcs1E9A0z2wECmZigG)。roles(ADMIN)。build();JdbcUserDetailsManagermanagernewJdbcUserDetailsManager(dataSource);manager。createUser(userA);manager。createUser(userB);}
  对于用户userA:
  使用用户userA的用户名和密码访问接口:curlHAuthorization:BasicdXNlckE6MTIzNDU2http:localhost:9999index
  认证通过,可正常访问。
  使用用户userA的用户名和密码访问接口helloname:curlHAuthorization:BasicdXNlckE6MTIzNDU2http:localhost:9999hellonamehelloname
  认证通过,鉴权通过,可正常访问。
  使用用户userA的用户名和密码访问接口helloworld:curlHAuthorization:BasicdXNlckE6MTIzNDU2http:localhost:9999helloworld{timestamp:20220110T13:11:18。03200:00,status:403,error:Forbidden,path:helloworld}
  认证通过,用户userA不属于角色ADMIN,禁止访问。
  使用用户userA的用户名和密码访问接口:curlHAuthorization:BasicdXNlckE6MTIzNDU2http:localhost:9999index
  认证通过,可正常访问。
  对于用户userB:
  使用用户userB的用户名和密码访问接口:curlHAuthorization:BasicdXNlckI6YWJjZGVmhttp:localhost:9999index
  认证通过,可正常访问。
  使用用户userB的用户名和密码访问接口helloworld:curlHAuthorization:BasicdXNlckI6YWJjZGVmhttp:localhost:9999helloworldhelloworld
  认证通过,鉴权通过,可正常访问。
  使用用户userB的用户名和密码访问接口helloname:curlHAuthorization:BasicdXNlckI6YWJjZGVmhttp:localhost:9999helloname{timestamp:20220110T13:18:29。46100:00,status:403,error:Forbidden,path:helloname}
  认证通过,用户userB不属于角色USER,禁止访问。
  这里可能会有一点奇怪,一般情况下我们会认为管理员应该拥有普通用户的全部权限,即普通用户可以访问接口helloname,那么管理员应该也是可以访问接口helloname的。如何实现呢?
  方式一,设置用户userB同时拥有角色USER和ADMIN;UserDetailsuserBUser。builder()。username(userB)。password({bcrypt}2a10PES8fUdtRrQ9OxLqf4CofOfcXBLQ3lkY2TSIcs1E9A0z2wECmZigG)。roles(USER,ADMIN)。build();
  这种方式有点不够优雅。
  方式二,设置角色ADMIN包含USER;
  SpringSecurity有一个HierarchicalRoles的特性,可以支持角色之间的包含操作。
  使用这个特性要特别注意两个地方:authorizeRequestsOverrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{http。authorizeRequests(authorizeauthorize。mvcMatchers(helloname)。hasRole(USER)。mvcMatchers(helloworld)。hasRole(ADMIN)。mvcMatchers()。authenticated())。httpBasic();}
  前文使用的是HttpSecurity。authorizeHttpRequests方法,此处需要变更为HttpSecurity。authorizeRequests方法。RoleHierarchyBeanRoleHierarchyhierarchy(){RoleHierarchyImplhierarchynewRoleHierarchyImpl();hierarchy。setHierarchy(ROLEADMINROLEUSER);}
  使用RoleHierarchy以Bean的方式定义角色之间的层级关系;其中,ROLE是SpringSecurity要求的固定前缀。
  编译启动应用,使用用户userB的用户名和密码访问接口helloname:curlHAuthorization:BasicdXNlckI6YWJjZGVmhttp:localhost:9999hellonamehelloname
  认证通过,鉴权通过,可正常访问。
  如果开启SpringSecurity的debug日志级别,访问接口时可以看到如下的日志输出:Fromtheroles〔ROLEADMIN〕onecanreach〔ROLEUSER,ROLEADMIN〕inzeroormoresteps。
  可以看出,SpringSecurity可以从角色ADMIN推导出用户实际拥有USER和ADMIN两个角色。特别说明
  HierarchicalRoles文档中的示例有明显错误:BeanAccessDecisionVoterhierarchyVoter(){RoleHierarchyhierarchynewRoleHierarchyImpl();hierarchy。setHierarchy(ROLEADMINROLESTAFFROLESTAFFROLEUSERROLEUSERROLEGUEST);returnnewRoleHierarcyVoter(hierarchy);}
  接口RoleHierarchy中并不存在方法setHierarchy。前文所述authorizeRequests和RoleHierarchy结合使用的方法是结合网络搜索和自身实践得出的,仅供参考。
  另外,authorizeHttpRequests和RoleHierarchy结合是没有效果的,authorizeRequests和authorizeHttpRequests两者之间的区别可以分别参考AuthorizeHttpServletRequestswithAuthorizationFilter和AuthorizeHttpServletRequestwithFilterSecurityInterceptor。
  鉴权的前提需要认证通过;认证不通过的状态码为401,鉴权不通过的状态码为403,两者是不同的。SpringSecurity异常处理器
  SpringSecurity异常主要分为两种:认证失败异常和鉴权失败异常,发生异常时会分别使用相应的默认异常处理器进行处理,即:认证失败异常处理器和鉴权失败异常处理器。
  使用的认证或鉴权实现机制不同,可能使用的默认异常处理器也不相同。认证失败异常处理器
  SpringSecurity认证失败异常处理器:publicinterfaceAuthenticationEntryPoint{voidcommence(HttpServletRequestrequest,HttpServletResponseresponse,AuthenticationExceptionauthException)throwsIOException,ServletE}
  如前文所述,认证失败时,SpringSecurity使用默认的认证失败处理器实现返回:{timestamp:20220110T02:47:20。82000:00,status:401,error:Unauthorized,path:}
  如果想要自定义返回内容,则可以通过自定义认证失败处理器实现:AuthenticationEntryPointauthenticationEntryPoint(){return(request,response,authException)response。getWriter()。print(401);}Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{http。。。。httpBasic()。authenticationEntryPoint(authenticationEntryPoint());}
  authenticationEntryPoint()会创建返回一个自定义的AuthenticationEntryPoint实例;其中,使用HttpServletResponse。getWriter()。print()写入我们想要返回的内容:401。
  httpBasic()。authenticationEntryPoint(authenticationEntryPoint())使用我们自定义的AuthenticationEntryPoint替换HttpBasic默认的BasicAuthenticationEntryPoint。
  编译启动应用,使用不正确的用户名和密码访问接口:curlHAuthorization:Basicerrorhttp:localhost:9999401
  认证不通过,使用我们自定义的内容401返回。鉴权失败异常处理器
  SpringSecurity鉴权失败异常处理器:publicinterfaceAccessDeniedHandler{voidhandle(HttpServletRequestrequest,HttpServletResponseresponse,AccessDeniedExceptionaccessDeniedException)throwsIOException,ServletE}
  如前文所述,认证失败时,SpringSecurity使用默认的认证失败处理器实现返回:{timestamp:20220110T13:18:29。46100:00,status:403,error:Forbidden,path:helloname}
  如果想要自定义返回内容,则可以通过自定义鉴权失败处理器实现:AccessDeniedHandleraccessDeniedHandler(){return(request,response,accessDeniedException)response。getWriter()。print(403);}Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{http。。。。httpBasic()。authenticationEntryPoint(authenticationEntryPoint())。and()。exceptionHandling()。accessDeniedHandler(accessDeniedHandler());}
  自定义鉴权失败处理器与认证失败处理器过程类似,不再赘述。
  编译启动应用,使用用户userA的用户名和密码访问接口helloworld:curlHAuthorization:BasicdXNlckE6MTIzNDU2http:localhost:9999helloworld403
  鉴权不通过,使用我们自定义的内容403返回。特别注意
  exceptionHandling()也是有一个authenticationEntryPoint()方法的;对于HttpBasic而言,使用exceptionHandling()。authenticationEntryPoint()设置自定义认证失败处理器是不生效的,具体原因需要大家自行研究。SpringSecurity自定义认证
  前文介绍两种认证方式:FormLogin和HttpBasic,SpringSecurity还提供其他若干种认证方式,详情可参考AuthenticationMechanisms。
  如果我们想实现自己的认证方式,也是比较简单的。SpringSecurity本质就是过滤器,我们可以实现自己的认证过滤器,然后加入到SpringSecurity中即可。FilterpreAuthenticatedFilter(){return(servletRequest,servletResponse,filterChain){。。。UserDetailsuserUser。builder()。username(xxx)。password(xxx)。roles(USER)。build();UsernamePasswordAuthenticationTokentokennewUsernamePasswordAuthenticationToken(user,user。getPassword(),user。getAuthorities());SecurityContextcontextSecurityContextHolder。createEmptyContext();context。setAuthentication(token);SecurityContextHolder。setContext(context);filterChain。doFilter(servletRequest,servletResponse);};}
  认证过滤器核心实现流程:利用Http请求(servletRequest)中的信息完成自定义认证过程(省略),可能的情况:检查请求中的用户名和密码是否匹配检查请求中的Token是否有效其他
  如果认证成功,则继续下一步;认证失败,则可以抛出异常,或者跳过后续步骤;从Http请求中提取username(用户名),使用已注入的UserDetailsService实例,加载UserDetails(用户信息)(省略);简单起见,模拟创建一个用户信息实例因为到这一步时,用户已是认证成功的,用户名和密码可以随意设置,实际只有角色是必须的,我们设置已认证用户的角色为USER。创建用户认证标识;SpringSecurity内部是依靠Authentication。isAuthenticated()来判断用户是否已认证过的,UsernamePasswordAuthenticationToken是Authentication的一种具体实现,需要注意创建实例时使用的构造方法和参数,构造方法内部会调用Authentication。setAuthenticated(true)。创建并设置环境上下文SecurityC环境上下文中保存着用户认证标识:context。setAuthentication(token)。特别注意
  除去抛出异常的情况外,filterChain。doFilter(servletRequest,servletResponse);是必须保证被执行的。
  理解认证过滤器涉及的概念会比较多,详情参考ServletAuthenticationArchitecture。
  认证过滤器创建完成之后,就可以加入到SpringSecurity中:Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{http。。。。。。。addFilterBefore(preAuthenticatedFilter(),ExceptionTranslationFilter。class)。exceptionHandling()。authenticationEntryPoint(authenticationEntryPoint())。accessDeniedHandler(accessDeniedHandler());}
  SpringSecurity根据我们配置的不同,会为我们自动按照一定的次序组装一条过滤器链,通过这条链上的若干过滤器完成认证鉴权的。我们需要把自定义的认证过滤器加到这个链的合适位置,这是选取的位置是在ExceptionTranslationFilter的前面。
  过滤器链的顺序可以参考SecurityFilters。
  ExceptionTranslationFilter的作用可以参考HandlingSecurityExceptions。特别注意
  使用自定义认证过滤器时,自定义认证失败异常处理器和鉴权失败异常处理器的设置方法。
  编译启动应用,我们会发现可以在不填入任何认证信息的情况下直接访问接口和helloname,因为模拟用户已认证且角色为USER;访问接口helloworld时会出现提示403。结语
  SpringSecurity自身包含的内容很多,官方文档也不能很好的讲述清楚每个功能特性的使用方法,很多时候需要我们自己根据文档、示例、源码以及他人的分享,尽可能多的实践,逐步加深理解。
投诉 评论 转载

遇到呼死你电话轰炸及短信轰炸怎么办?谢邀!遇上电话轰炸要就是把电话调静音,无关重要的人就直接拉黑如今大多数用的都是智能手机,智能手机有一个功能就是自动拦截,当倍呼后,我们就可以开启这个功能,里面有多种选项:……现在苹果6系列的手机,微信启动为什么特慢?悟空问答,等我来答!很高兴参与问答。可能你没有关注沸沸扬扬的苹果降频门事件,你的手机应该就属于中这种的情况。简单的说就是因为苹果IOS系统有一个保护机制,在你的手机使用的……小米笔记本Pro系列增强版红米笔记本Pro14Pro15增强1、小米笔记本Pro15增强版:酷睿i511320H处理器i711390H处理器,可选MX450独显搭配16G双通道DDR43200内存512G(PCIe)SSD……创维汽车邀您共赏2021第34届大河国际车展10月26日,2021第34届大河国际车展在郑州国际会展中心盛大举办,创维汽车携旗下首款车型创维EV6正式亮相大河车展。创维汽车EV6推出4款车型,分为出行版、标准版、智……支付宝微信个人收款码要被追查近4年数据,还要补税?谣言新京报贝壳财经讯(记者程维妙潘亦纯)3月1日个人收款条码使用新规即将实施。日前有网传消息称,自2022年3月1日起,微信、支付宝的个人收款码不得用于经营性收款。近4年的数据将被……小米平板5已正式入网工信部中关村在线消息:7月7日中午,知名数码博主数码闲聊站爆料,小米新平板已正式入网工信部,目前只有配备了高通骁龙870移动平台的5G高配版入网,配备高通骁龙860移动平台的低配版还……美联储发行央行数字货币比特币迎来大幅下跌【美联储首次定调:发行央行数字货币有助于保持美元的国际主导地位】1月21日消息,根据北京时间1月21日美联储发布的央行数字货币(CBDC)利弊讨论文件《货币与支付:数字转型时代……3月我国新能源汽车销量同比增长1。1倍央广网北京4月12日消息中国汽车工业协会近日发布了2022年3月汽车工业经济运行情况。3月,受新冠肺炎疫情、芯片供应短缺等因素影响,我国汽车产销同比分别下降9。1和11。7。新……SpringSecurity接口认证鉴权入门实践指南前言WebAPI接口服务场景里,用户的认证和鉴权是很常见的需求,SpringSecurity据说是这个领域里事实上的标准,实践下来整体设计上确实有不少可圈可点之处,也在一……努比亚Z30Pro发布,久违的中兴旗舰努比亚的旗舰手机淡出大家的视野有一段时间了,这次新发布的努比亚Z30Pro可以说是相当有分量,别的不说,拿到手之后就非常的重量级,这次努比亚Z30Pro随机赠送了120W氮化镓……如果华为又有最新的芯片了,那你还会继续支持华为吗?就是现在华为被美国卡脖子,断供芯片,我也支持华为。更别说华为以后又有芯片了。支持华为是我的不二选择。因为,他是百分之百的民族品牌,民族企业。不像联想、阿里巴巴和中兴,里边……vivoX70Pro评测可能是今年拍照最好的手机前言:早先的时候,笔者对于手机影像方面的态度一直都是还行就行,毕竟手机拍照的使用场景更多的是便携和快速的分享出去,而并非是专业,各个平台动辄把一张照片和视频压缩的完全不能……
等等党终将胜利!蔚来新品牌杀入平民市场,比特斯拉便宜还服务好为何说IT科技公司应该留住35岁员工?小米出价格5000元以上的手机你会买吗?宁德时代又急了钱放微信还是余额宝好?全球首富马斯克!粉丝突破9042万,为什么要重金收购推特?在武汉当一名出租车司机,月收入能过4000吗?以画笔展现科技成就,为科技工作者立传飞书以先进工具塑造新组织形态,让信息如水一般流动WISE20iPhone13保护壳曝光,外观基本上确定了小鹏汽车2021年营收210亿元,同比增长259青花郎暂停接收订单天猫已告罄下架汪俊林此前称卖完了就没有了

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找七猫云易事利