上一次写到使用spring-security做简单登录应用,先补交家庭作业
如何自定义登录页面#####
- 修改
WebSecurityConfig
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/user/**").hasAnyRole("ADMIN", "USER")
.and()
.formLogin().loginPage("/login").loginProcessingUrl("/login.do").defaultSuccessUrl("/user/info")
.failureUrl("/login?err=1")
.permitAll()
.and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
.permitAll()
;
}
解释看代码字面意思就懂了,没什么特殊的,还可以修改比如登录表单里的用户名和密码的名字,还可以添加各种登录成功之后的handler
等等,写法都一样。
- 添加
LoginController
@Controller
public class LoginController {
@RequestMapping("/login")
public String login(String err, ModelMap modelMap) {
if (StringUtils.hasLength(err)) {
modelMap.put("err", err);
}
return "login";
}
}
- 添加页面就是一个普通的form表单
<form action="/login.do" method="post">
<input type="hidden" th:value="${_csrf.token}" name="_csrf"/>
<div class="form-group">
<label for="usernameInput">Username</label>
<input type="text" id="usernameInput" class="form-control" name="username" placeholder="enter your username"/>
</div>
<div class="form-group">
<label for="passwordInput">Password</label>
<input type="password" id="passwordInput" class="form-control" name="password" placeholder="enter your password"/>
</div>
<div class="form-group" th:if="${err}">
<div class="text-danger ">
用户名或密码错误
</div>
</div>
<div class="form-group text-center">
<button type="submit" class="btn btn-primary">Login</button>
<button type="reset" class="btn btn-warning">Reset</button>
</div>
</form>
这里需要注意的就是表单里有一个隐藏的input叫_csrf
是用来防范跨域攻击的,详情查看CSRF。可以在配置类关掉.csrf().disable()
,当然是不推荐的。
以上代码地址:v1.0
oauth2#####
上面的代码应该满足一般意义上的网站登录,下面简单介绍怎么使用
spring-security-oauth
这东西比较复杂,分两部分,先讲一部分。
至于OAuth
是什么东西,请问百度或谷歌,官方地址在https://oauth.net/2/
OAuth
简单来说是一种协议。在我看来就是对上篇文章所说的登录鉴权模式的一种补充(当然不是严格意义的说,我只是想简单明了的阐述一下这是一个什么东西),大家都知道以前系统是分为两种模式的——B/S和C/S,上篇文章讲述的都是B/S模式下的登录授权方式,但是对于C端即Client是不好使的,所以就有了OAuth
协议(来历纯属虚构),那么什么情况下会使用呢?——虽然感觉很神秘,但是这个协议应用却很常见——几乎所有的大公司都在使用,比如微信登录、微博登录、GitHub登录、Google登录、QQ登录、FaceBook登录、Twitter登录等等这种第三方登录都是使用的这个协议,可以看看微博登录的地址即可:
https://api.weibo.com/oauth2/authorize?scope=email&state=f4dde19fcea5b2ab8c3e682da17a511d&redirect_uri=http%3A%2F%2Fwww.zhihu.com%2Foauth%2Fcallback%2Fsina&response_type=code&client_id=3063806388
oauth2协议流程######
在网上盗的别人的一个图:
- Client向服务器发起OAuth请求交换
authorization_code
,需要携带的参数:-
client_id
:可以理解为客户端用于登录的用户名 -
response_type
:本次请求什么?一般首次请求为code
-
redirect_uri
:认证成功返回的地址 -
scope
:权限范围,指本次授权获取资源的权限范围,比如只读,可读写之类 -
state
:一般为随机数,可选,服务器会原样返回,用于客户端验证服务器
这些参数名称以及值不是严格意义不变的,各个认证服务器都会有自己的风格。
-
- 如果是浏览器发起的第三方登录,比如上述举例的在知乎上使用微博登录,输入微博的用户名和密码,验证通过之后,则服务器会自动从微博重定向到刚才的
redirect_uri
,严谨一点的服务器还会询问你是否允许比如知乎请求你的微博个人信息。如果不是浏览器比如手机APP,服务器会直接返回code
。 - 使用
code
获取access_token
,这一步一般是在客户端的服务器(不是第三方认证服务器,比如上文的知乎浏览器即客户端,知乎的服务器即客户端的服务器,微博即第三方认证服务器)进行的,就是一般来说用户是无感知的(不是所有的认证服务器都有这一步,只是更加安全而已,服务器实现方式不同,有的是直接可以使用用户名以及密码换取access_token的),一般需要携带的参数:-
client_id
:如上 -
client_secret
:可以理解为客户端的密码,做过APP的肯定都接触过使用微博登录的话是需要在微博申请app_id
和app_secret
的,其实对应就是client_id
和client_secret
-
code
: 第一步请求获取的authorization_code
-
grant_type
:认证类型,也可以理解为本次请求需要做什么,这个属性各个服务器定义非常不同,都不是按照OAuth
标准协议来的,各有各的任性,不过通常意义上都实现了下面五种类型:-
authorization_code
:即本次讲解的流程,需要先请求code
,再用code
换token
-
password
:直接使用用户名和密码交换token
-
refresh_token
:刷新token
-
implicit
:简单模式,一般用在JS等直接在浏览器里获取token的方式,当然密码也是直接放在参数里的,尽管加了密,想想也不太安全(经 @251be0f15727 纠正请看评论),很少用 -
client_credentials
:无用户模式,即直接客户端的服务器单凭自己的client_id
和client_secret
请求资源,一般用于请求一些服务器的非私密信息,使用极少
-
- 如果不是浏览器,还需要加上
username
和password
即用户的登录信息
-
上述1,2,3举得是标准全量流程,使用不同的客户端方式可能会有不同,但是大的流程是不变的。获取到access_token
后就可以使用token
向微博请求个人信息了,比如昵称和头像;也可以使用token
刷新access_token
。
使用GitHub登录实战演练#####
先演示如何搭建客户端,选择GitHub作为OAuth认证服务器,需要在GitHub Settings注册客户端
演示所用客户端配置如图
需要注意的参数主要是callback URL,需要和传递的参数完全一致,对应上面的
redirect_uri
解释。
修改WebSecurityConfig
:
@Autowired
OAuth2ClientContext oauth2ClientContext;
...
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/user/**").authenticated()
.anyRequest().permitAll()
.and().exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
// .and()
// .formLogin().loginPage("/login").loginProcessingUrl("/login.do").defaultSuccessUrl("/user/info")
// .failureUrl("/login?err=1")
// .permitAll()
.and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
.permitAll()
.and().addFilterBefore(sso(), BasicAuthenticationFilter.class)
;
}
private Filter sso() {
OAuth2ClientAuthenticationProcessingFilter githubFilter = new OAuth2ClientAuthenticationProcessingFilter("/login/github");
OAuth2RestTemplate githubTemplate = new OAuth2RestTemplate(github(), oauth2ClientContext);
githubFilter.setRestTemplate(githubTemplate);
githubFilter.setTokenServices(new UserInfoTokenServices(githubResource().getUserInfoUri(), github().getClientId()));
return githubFilter;
}
@Bean
@ConfigurationProperties("github.resource")
public ResourceServerProperties githubResource() {
return new ResourceServerProperties();
}
@Bean
@ConfigurationProperties("github.client")
public AuthorizationCodeResourceDetails github() {
return new AuthorizationCodeResourceDetails();
}
@Bean
public FilterRegistrationBean oauth2ClientFilterRegistration(
OAuth2ClientContextFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
这就是主要代码了,标准流程spring-security-oauth2
已经都帮我们写好了,代码解释:
-
addFilterBefore(sso(), BasicAuthenticationFilter.class)
在网站基本认证之前添加OAuth2ClientAuthenticationProcessingFilter
,上面介绍的OAuth
流程基本都在这个Filter
里,建议翻看源码有助于理解 -
@ConfigurationProperties
注解是方便通过配置文件生成所需类,这里因为OAuth
配置比较复杂,故将properties
文件改用YAML
模式,具体配置下面讲解。 - 注册一个额外的
Filter
:OAuth2ClientContextFilter
,主要作用是重定向,当遇到需要权限的页面或URL,代码抛出异常,这时这个Filter
将重定向到OAuth
鉴权的地址,本文即/login/github
- 之后向GitHub获取
code
包括token
的功能spring
都帮我们封装好了,非常方便,如果要自定义,只要重写对应的接口即可,就像上篇文章里重写UserDetailsService
是一样的
配置文件:
github:
client:
clientId: fd57ea20f71057e0f396
clientSecret: 30b73085b6c726b5fb2e0fa8402846b72d86451f
accessTokenUri: https://github.com/login/oauth/access_token
userAuthorizationUri: https://github.com/login/oauth/authorize
authenticationScheme: query
clientAuthenticationScheme: form
# pre-established-redirect-uri: http://localhost:8090/login/github
# registered-redirect-uri: http://localhost:8090/login/github
# use-current-uri: false
resource:
userInfoUri: https://api.github.com/user
clientId
和clientSecret
在上文注册客户端的Github页面上有,accessTokenUri
是获取token
的地址,userAuthorizationUri
是登陆之后询问的地址,只是第一次会有,张这样:
使用过微博登录的同学肯定也见过微博的这个页面,现在大家应该知道这个页面是怎么来了的吧,后面2个
Schema
是指请求参数以什么样的方式跟随,有query
(跟在url参数后面),form
(以form的body形式提交),header
(放到Http header里),none
(没有,我也不知道这个是什么意思),注释的3个是测试回调地址的,默认是将当前url地址作为redirect_uri
,因为有些服务器是允许多个回调地址的,这个具体看服务器自己怎么设定。这些参数都可以在对应ResourceServerProperties
和AuthorizationCodeResourceDetails
的源码找到。
最后,页面代码就不贴了,就是一个a
标签,链接到/login/github
,运行成功的话,在user页面应该可以看到你的github的用户名
本文到此结束,再写实在太长了,下文将介绍如何使用
spring-security-oauth2
做OAuth Server
,我想这才是重点。另外提一点,上面的代码不完整,只支持github登录(有兴趣的同学可以添加诸如FaceBook登录啊什么的,改改配置文件和地址就行了),本来的admin/admin
将失去效果,可以使用ajax提交form的形式代替,就几行代码:
@RequestMapping(value = "/ajaxLogin", method = RequestMethod.POST)
@ResponseBody
public String ajaxLogin(@RequestParam String username, @RequestParam String password) {
try {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
token.setDetails(loginService.loadUserByUsername(username));
Authentication auth = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(auth);
return JsonResponse.returnResult(true);
} catch (UsernameNotFoundException e1) {
LOGGER.error("ajax login error, username not found", e1);
return JsonResponse.returnMsg(false,"login.passwordError");
} catch (BadCredentialsException e2) {
LOGGER.error("ajax login error, password not right", e2);
return JsonResponse.returnMsg(false,"login.passwordError");
} catch (Exception e){
LOGGER.error("ajax login error. forbidden login status", e);
return JsonResponse.returnMsg(false,"login.forbidden");
}
}
这里后面会搭建OAuth server
就不赘述了,代码打了tag
在github v1.2。
PS:上面的代码其实为了方遍理解OAuth
流程,spring有个注解叫@EnableOAuth2Sso
(这里用的是它的子集EnableOAuth2Client
)可以一键搞定,一行代码都不用写,有兴趣的可以看一看。.