1.初次使用

模块名称 default-demo
功能描述 完成登录后,才能对接口进行访问

1.1依赖导入

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>
  5. <!--security-->
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-security</artifactId>
  9. </dependency>

1.2配置类

  1. @EnableWebSecurity
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  3. }

1.3Controller接口

  1. @RestController
  2. public class DefaultDemoController {
  3. @GetMapping("query")
  4. public String query(){
  5. return "stu list";
  6. }
  7. }

1.4启动后控制台

这里会为我们自动生成一段密码
image.png

1.5通过浏览器访问 query 接口

①浏览器输入:localhost:8080/query
②页面会自动跳转到:一个登录页(localhost:8080/login)
image.png
③输入用户名:user,用户密码:f30bcf90-bd54-4ae0-a934-5dc66ba8dd34(控制台打印出来的)进行登录,即可完成登录,并自动跳转到 query接口
image.png

1.6框架做了什么?

①用户输入localhost:8080/query
②此接口请求被springSecurity框架中的过滤器拦截,并重定向页面到一个(localhost:8080/login)上。
③用户输入了用户名和密码
④SpringSecurity框架中的代码进行用户名密码的校验。
⑤校验失败,返回登录失败
⑥校验成功,跳转到之前访问的 query接口上

1.7以上流程中需要自定义东西

①当用户未登录时,我希望跳转到我指定的登录页面。
②用户的校验逻辑需要我自己去定义(查询数据库校验)

2.自定义登录页面

2.1定义一个登录页面

image.png

2.2修改配置类

  1. @EnableWebSecurity
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Autowired
  4. private MyLoginFailureHandler myLoginFailureHandler;
  5. @Override
  6. protected void configure(HttpSecurity http) throws Exception {
  7. //页面名称必须加 /
  8. http.formLogin().loginPage("/mylogin.html")
  9. .and()
  10. .authorizeRequests().antMatchers("/mylogin.html").permitAll()
  11. .and()
  12. .authorizeRequests().anyRequest().authenticated(); //所有的请求都需要认证后访问
  13. }
  14. }

2.3访问query接口

image.png

2.4进行登录

没有效果,仍然停留在 mylogin.html这个登录页。
问题原因:指定了登录页面,但是没有指定 处理登录逻辑的 url地址
解决方式修改配置文件
image.pngimage.png

2.5表单字段不一致问题

image.png
①解决方式1:将字段名成换成username、password
②解决方式2:修改SpringSecurity在登录时获取的参数名称

image.png

2.6还是登录 不了

添加关闭跨域保护

2.7登录失败后的逻辑

默认逻辑是跳转到 mylogin?error页面
设置一个登录失败处理器

  1. @Component
  2. public class MyLoginFailureHandler implements AuthenticationFailureHandler {
  3. @Override
  4. public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
  5. response.getWriter().println("登录失败");
  6. }
  7. }

配制类
image.png

2.8登录成功后的逻辑

默认是跳转回原页面、或者回到 主页面(index.html)
配置
image.png

  1. @Component
  2. public class MyLoginSuccessHandler implements AuthenticationSuccessHandler {
  3. @Override
  4. public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
  5. response.setCharacterEncoding("UTF-8");
  6. response.getWriter().println("登录成功");
  7. }
  8. }

3.自定义登录逻辑

在真实的场景中,项目会有一个用户信息,里面有用户的密码,用户名登录信息。这些信息一定是从数据库中取得的。

3.1原始SpringSecurity框架在我们点击登录后为我们做了什么

1.首先进入 UsernamePasswordAuthenticationFilterpublic ``Authentication ``attemptAuthentication``_(_``HttpServletRequest request``,`` ``HttpServletResponse response``_) _``throws ``AuthenticationException 的方法中。方法做了以下事情
①获取用户名、密码
②生成UsernamePasswordAuthenticationToken
③调用 AuthenticationManager去认证这个 UnamepwdAuthenToken
2.Authentication的实现类(ProviderManager),执行自己的 public Authentication authenticate(Authentication authentication) ,这个方法中做了以下事务
①获取这个 unamepwdToken的字节码对象
②寻找一个可以认证这个 unamepwdToken的 Provider
③调用provider的认证方法,返回Authenticaiton对象
3.Provider是进行unamepwdToken的验证,其中一个实现类(RemoteAuthenticationProvider)provider的support方法用来判断unamepwdtokne是否是我所能够处理的类型。接下来看下Provider的处理方法:
①从unametoken中去得用户名
②从Credentials中去得用户的密码
③通过认证 Manager中尝试授权,获取 角色集合(这里用的就是 UserDetailService,并返回一个UserDetail对象)
④将用户名密码、和角色重新封装成 unamepwdToken
4.UserDetailService提供的就是根据用户名查询 用户角色的功能,并返回一个角色的集合的方法

3.2需求描述

1.query方法需要有Admin角色才能够访问,
2.数据库中有 sysUser表和role表
3.重写UserDetailService和UserDetail

3.3角色设置了,不起作用的原因

需要将角色前添加 ROLE_ 的前缀才能生效。

3.4三种权限注解

①JSR-250注解:@DenyAll @RoleAllows()@PermitAll
②。。。

4.图片验证码

1.设置前置过滤器进行校验
2.实现认证失败处理类,完成认证失败时的返回

5.自定义登录逻辑

模仿智联招聘登录,用户仅凭手机号和验证码就可以实现登录
①短信验证码,使用图形验证码模拟
②框架登录逻辑需要密码比对

5.1编写处理短信验证码的过滤器

参考UsernamePasswordkFilter

5.2编写SmsToken

参考UserNameToken

5.3编写Provider

参考xxProvider

5.4编写在Provider中使用的UserDetailService以及UserDetail

在provider中的authenticate方法中会调用 userDetailService的loginByusername,返回userDetail

6.用户名密码登录的逻辑流程(源码级别)

image.png
1.登录请求进入UsernamePasswordAuthenticationFilter中。
2.获取用户名、密码封装成一个未认证的 token
3.将token放入 认证管理中 进行认证
4.认证管理器的一个实现类(providerManager)的authenticate方法
5.providermanager选择一个支持这种token的Provider进行校验
6.用户密码选择 了一个 DaoAuthenticationProvider(实现了AbstractUserDetailsAuthenticationProvider)的一个实体类
7.DaoAuthenticationProvider中的 authenticate方法做了:
①从token的principles字段获取用户名

  1. String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
  2. : authentication.getName();

②先尝试从缓存中获取
③调用retriveuser方法

  1. protected final UserDetails retrieveUser(String username,
  2. UsernamePasswordAuthenticationToken authentication)
  3. throws AuthenticationException {
  4. prepareTimingAttackProtection();
  5. try {
  6. //通过username查询用户的 信息 (UserDetail)
  7. UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
  8. if (loadedUser == null) {
  9. throw new InternalAuthenticationServiceException(
  10. "UserDetailsService returned null, which is an interface contract violation");
  11. }
  12. //返回UserDetail信息
  13. return loadedUser;
  14. }
  15. catch (UsernameNotFoundException ex) {
  16. mitigateAgainstTimingAttack(authentication);
  17. throw ex;
  18. }
  19. catch (InternalAuthenticationServiceException ex) {
  20. throw ex;
  21. }
  22. catch (Exception ex) {
  23. throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
  24. }
  25. }

④通过preAuthenticationChecks检查用户是否过期,禁用之类

  1. preAuthenticationChecks.check(user);

⑤additionalAuthenticationChecks 密码校验

  1. // 从数据库中查询的信息,token信息
  2. additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);
  3. ==============================================================================
  4. protected void additionalAuthenticationChecks(UserDetails userDetails,
  5. UsernamePasswordAuthenticationToken authentication)
  6. throws AuthenticationException {
  7. String presentedPassword = authentication.getCredentials().toString();
  8. //若密码不匹配
  9. if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
  10. logger.debug("Authentication failed: password does not match stored value");
  11. throw new BadCredentialsException(messages.getMessage(
  12. "AbstractUserDetailsAuthenticationProvider.badCredentials",
  13. "Bad credentials"));
  14. }
  15. }

⑥返回一个Tokne,pricipal就是 从数据库中查询的userDetail,authentication是原始token,从中去得密码,user也是userDetail查询的。

7.Oauth2.0授权码模式

这里插入一条关于权限注解的相关内容,使用注解时,不要再用http.对请求进行指定了。

7.1项目搭建

1.pom文件导入

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.cloud</groupId>
  7. <artifactId>spring-cloud-starter-oauth2</artifactId>
  8. </dependency>
  9. <dependency>
  10. <groupId>org.projectlombok</groupId>
  11. <artifactId>lombok</artifactId>
  12. </dependency>

2.Security配置文件

  1. @EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true,jsr250Enabled = true)
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter {
  3. }

3.认证服务器配置文件

  1. @Configuration
  2. @EnableAuthorizationServer
  3. public class AuthorizationServerConfig {
  4. }

4.controller的几个方法

  1. @RestController
  2. public class UserController {
  3. /**
  4. * 回调方法
  5. * @param code
  6. * @return
  7. */
  8. @RequestMapping("getCode")
  9. public String getCode(String code){
  10. return code;
  11. }
  12. }

5.配置文件

  1. security:
  2. oauth2:
  3. client:
  4. client-id: yurun
  5. client-secret: yurun
  6. registered-redirect-uri: http://localhost:8080/getCode
  7. scope: all

7.2运行步骤

1.通过浏览器访问(http://localhost:8080/oauth/authorize?response_type=code&client_id=yurun&redirct_uri=http://localhost:8080/getCode&scope=all
2.跳转到登录页面,并完成登录
3.输入用户名和密码,会跳转到 getcode接口,并携带一个code
4.请求token
image.png
image.png
5.返回的结果

  1. {
  2. "access_token": "abe04eb7-5dbe-4bb5-8606-68049436cdb4",
  3. "token_type": "bearer",
  4. "refresh_token": "03e96129-f94f-4537-bb00-d4eeb56c1022",
  5. "expires_in": 43199,
  6. "scope": "all"
  7. }

8.密码模式

8.1请求token

image.png

image.png

8.2报错

image.png

8.3对SecurityConfig配置类修改

  1. @Bean
  2. @Override
  3. public AuthenticationManager authenticationManagerBean() throws Exception {
  4. return super.authenticationManagerBean();
  5. }

image.png

9.资源服务器的配置

9.1在认证服务器上同时设有资源服务器

  1. @Configuration
  2. @EnableResourceServer
  3. public class ResourceServerConfig {
  4. }

9.2产生问题

资源服务器的存在会让其服务器上的所有接口需要令牌反问,当然包括 login接口。所以我们需要设置login接口。被允许

  1. @Configuration
  2. @EnableResourceServer
  3. public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
  4. @Override
  5. public void configure(HttpSecurity http) throws Exception {
  6. http.formLogin().and().authorizeRequests().antMatchers("/login").permitAll();
  7. }
  8. }