Maison > interface Web > js tutoriel > le corps du texte

Problèmes avec SpringBoot et SpringSecurity gérant les demandes de connexion Ajax

不言
Libérer: 2018-06-25 10:31:17
original
1779 Les gens l'ont consulté

Cet article présente principalement le problème de la gestion des demandes de connexion Ajax par SpringBoot+SpringSecurity. Cet article vous présente très bien et a une valeur de référence. Les amis qui en ont besoin peuvent s'y référer

J'ai récemment rencontré cela dans le projet. Un problème : le front-end et le back-end sont séparés. Le front-end est réalisé avec Vue. Toutes les demandes de données utilisent vue-resource et aucun formulaire n'est utilisé. Par conséquent, l'interaction des données utilise JSON Spring Boot en arrière-plan. . Spring Security est utilisé pour la vérification des autorisations. Parce qu'il a été utilisé avant que Spring Security ne gère les pages, cette fois, il ne gère que les requêtes Ajax, donc certains problèmes rencontrés sont enregistrés. La solution ici convient non seulement aux requêtes Ajax, mais résout également la vérification des requêtes mobiles.

Créer un projet

Nous devons d'abord créer un projet Spring Boot Lors de la création, nous devons introduire Web, Spring Security, MySQL et. MyBatis (framework de base de données En fait, n'hésitez pas, j'utilise MyBatis ici) Après création, les fichiers de dépendances sont les suivants :

<dependency>
 <groupId>org.mybatis.spring.boot</groupId>
 <artifactId>mybatis-spring-boot-starter</artifactId>
 <version>1.3.1</version>
</dependency>
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <scope>runtime</scope>
</dependency>
<dependency>
 <groupId>commons-codec</groupId>
 <artifactId>commons-codec</artifactId>
 <version>1.11</version>
</dependency>
Copier après la connexion

Faites attention au dernier). commons-codec J'ai ajouté la dépendance manuellement. Il s'agit d'un projet open source Apache qui peut être utilisé pour générer des résumés de messages MD5. Je traiterai brièvement le mot de passe dans l'article suivant.

Créer la base de données et la configurer

Afin de simplifier la logique, j'ai créé ici trois tables, à savoir la table des utilisateurs, la table des rôles et rôle de l'utilisateur La table d'association est la suivante :

Ensuite, nous devons simplement configurer notre base de données dans application.properties Ici, vous dépendrez de votre situation spécifique.

spring.datasource.url=jdbc:mysql:///vueblog
spring.datasource.username=root
spring.datasource.password=123
Copier après la connexion

Construction de classes d'entités

Ici, il s'agit principalement de la construction de classes d'utilisateurs. La classe d'utilisateurs ici est assez particulière et doit implémenter l'interface UserDetails, comme suit :

public class User implements UserDetails {
 private Long id;
 private String username;
 private String password;
 private String nickname;
 private boolean enabled;
 private List<Role> roles;
 @Override
 public boolean isAccountNonExpired() {
 return true;
 }
 @Override
 public boolean isAccountNonLocked() {
 return true;
 }
 @Override
 public boolean isCredentialsNonExpired() {
 return true;
 }
 @Override
 public boolean isEnabled() {
 return enabled;
 }
 @Override
 public List<GrantedAuthority> getAuthorities() {
 List<GrantedAuthority> authorities = new ArrayList<>();
 for (Role role : roles) {
  authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
 }
 return authorities;
 }
 //getter/setter省略...
}
Copier après la connexion

Après avoir implémenté l'interface UserDetails, il existe plusieurs méthodes dans ce interface dont nous avons besoin pour la mise en œuvre, les quatre méthodes qui renvoient des booléens sont toutes connues par leur nom activé indique si le compte de planification est activé dans ma base de données, il est donc renvoyé en fonction des résultats de la requête. Par souci de simplicité, les autres méthodes renvoient directement true et la méthode getAuthorities renvoie les informations sur le rôle de l'utilisateur actuel. Le rôle de l'utilisateur est en fait les données des rôles. Convertissez simplement les données des rôles en List Il y a quelque chose à noter ici. Puisque les noms de rôle que je stocke dans la base de données sont tous tels que « super administrateur », « utilisateur ordinaire », etc. Cela ne commence pas par un caractère comme ROLE_, vous devez donc le faire. ajoutez manuellement ROLE_ ici, rappelez-vous.

Il existe également une classe d'entité Role, qui est relativement simple et peut être créée en fonction des champs de la base de données. Je n'entrerai pas dans les détails ici.

Créer un service utilisateur

Le UserService ici est également spécial et doit implémenter l'interface UserDetailsService, comme suit :

@Service
public class UserService implements UserDetailsService {
 @Autowired
 UserMapper userMapper;
 @Autowired
 RolesMapper rolesMapper;
 @Override
 public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
  User user = userMapper.loadUserByUsername(s);
  if (user == null) {
   //避免返回null,这里返回一个不含有任何值的User对象,在后期的密码比对过程中一样会验证失败
   return new User();
  }
  //查询用户的角色信息,并返回存入user中
  List<Role> roles = rolesMapper.getRolesByUid(user.getId());
  user.setRoles(roles);
  return user;
 }
}
Copier après la connexion

Après avoir implémenté l'interface UserDetailsService, nous devons implémenter la méthode loadUserByUsername dans cette interface, c'est-à-dire interroger l'utilisateur en fonction du nom d'utilisateur. Deux mappeurs dans MyBatis sont injectés ici, UserMapper est utilisé pour interroger les utilisateurs et RolesMapper est utilisé pour interroger les rôles. Dans la méthode loadUserByUsername, interrogez d'abord l'utilisateur en fonction des paramètres entrants (le paramètre est le nom d'utilisateur saisi lors de la connexion de l'utilisateur. Si l'utilisateur trouvé est nul, vous pouvez directement lancer une exception UsernameNotFoundException, mais pour faciliter le traitement). , j'ai renvoyé un objet A User sans aucune valeur, de sorte que l'échec de connexion sera toujours trouvé dans le processus de comparaison de mot de passe ultérieur (vous pouvez l'ajuster en fonction de vos propres besoins professionnels si l'utilisateur trouvé n'est pas nul, à ce moment-là). nous le trouverons en fonction de l'interrogation Ensuite, du rôle de l'utilisateur avec l'identifiant de l'utilisateur et placerons les résultats de la requête dans l'objet utilisateur. Ce résultat de la requête sera utilisé dans la méthode getAuthorities de l'objet utilisateur.

Configuration de sécurité

Jetons d'abord un coup d'œil à ma configuration de sécurité, puis je l'expliquerai une par une :

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 @Autowired
 UserService userService;
 @Override
 protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth.userDetailsService(userService).passwordEncoder(new PasswordEncoder() {
   @Override
   public String encode(CharSequence charSequence) {
    return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes());
   }
   /**
    * @param charSequence 明文
    * @param s 密文
    * @return
    */
   @Override
   public boolean matches(CharSequence charSequence, String s) {
    return s.equals(DigestUtils.md5DigestAsHex(charSequence.toString().getBytes()));
   }
  });
 }
 @Override
 protected void configure(HttpSecurity http) throws Exception {
  http.authorizeRequests()
    .antMatchers("/admin/**").hasRole("超级管理员")
    .anyRequest().authenticated()//其他的路径都是登录后即可访问
    .and().formLogin().loginPage("/login_page").successHandler(new AuthenticationSuccessHandler() {
   @Override
   public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
    httpServletResponse.setContentType("application/json;charset=utf-8");
    PrintWriter out = httpServletResponse.getWriter();
    out.write("{\"status\":\"ok\",\"msg\":\"登录成功\"}");
    out.flush();
    out.close();
   }
  })
    .failureHandler(new AuthenticationFailureHandler() {
     @Override
     public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
      httpServletResponse.setContentType("application/json;charset=utf-8");
      PrintWriter out = httpServletResponse.getWriter();
      out.write("{\"status\":\"error\",\"msg\":\"登录失败\"}");
      out.flush();
      out.close();
     }
    }).loginProcessingUrl("/login")
    .usernameParameter("username").passwordParameter("password").permitAll()
    .and().logout().permitAll().and().csrf().disable();
 }
 @Override
 public void configure(WebSecurity web) throws Exception {
  web.ignoring().antMatchers("/reg");
 }
}
Copier après la connexion

C'est le cœur de notre configuration, les amis, écoutez-moi un par un :

1. Tout d'abord, c'est une configuration. classe, alors n'oubliez pas d'ajouter l'annotation @Configuration, et comme il s'agit de la configuration de Spring Security, n'oubliez pas d'hériter de WebSecurityConfigurerAdapter.

2. Injectez le UserService que vous venez de créer, nous l'utiliserons plus tard.

3.configure(AuthenticationManagerBuilder auth)方法中用来配置我们的认证方式,在auth.userDetailsService()方法中传入userService,这样userService中的loadUserByUsername方法在用户登录时将会被自动调用。后面的passwordEncoder是可选项,可写可不写,因为我是将用户的明文密码生成了MD5消息摘要后存入数据库的,因此在登录时也需要对明文密码进行处理,所以就加上了passwordEncoder,加上passwordEncoder后,直接new一个PasswordEncoder匿名内部类即可,这里有两个方法要实现,看名字就知道方法的含义,第一个方法encode显然是对明文进行加密,这里我使用了MD5消息摘要,具体的实现方法是由commons-codec依赖提供的;第二个方法matches是密码的比对,两个参数,第一个参数是明文密码,第二个是密文,这里只需要对明文加密后和密文比较即可(小伙伴如果对此感兴趣可以继续考虑密码加盐)。

4.configure(HttpSecurity http)用来配置我们的认证规则等,authorizeRequests方法表示开启了认证规则配置,antMatchers("/admin/**").hasRole("超级管理员")表示 /admin/** 的路径需要有‘超级管理员'角色的用户才能访问,我在网上看到小伙伴对hasRole方法中要不要加 ROLE_ 前缀有疑问,这里是不要加的,如果用hasAuthority方法才需要加。anyRequest().authenticated()表示其他所有路径都是需要认证/登录后才能访问。接下来我们配置了登录页面为login_page,登录处理路径为/login,登录用户名为username,密码为password,并配置了这些路径都可以直接访问,注销登陆也可以直接访问,最后关闭csrf。在successHandler中,使用response返回登录成功的json即可,切记不可以使用defaultSuccessUrl,defaultSuccessUrl是只登录成功后重定向的页面,使用failureHandler也是由于相同的原因。

5.configure(WebSecurity web)方法中我配置了一些过滤规则,不赘述。

6.另外,对于静态文件,如 /images/**/css/**/js/** 这些路径,这里默认都是不拦截的。

Controller

最后来看看我们的Controller,如下:

@RestController
public class LoginRegController {

 /**
  * 如果自动跳转到这个页面,说明用户未登录,返回相应的提示即可
  * <p>
  * 如果要支持表单登录,可以在这个方法中判断请求的类型,进而决定返回JSON还是HTML页面
  *
  * @return
  */
 @RequestMapping("/login_page")
 public RespBean loginPage() {
  return new RespBean("error", "尚未登录,请登录!");
 }
}
Copier après la connexion

这个Controller整体来说还是比较简单的,RespBean一个响应bean,返回一段简单的json,不赘述,这里需要小伙伴注意的是 login_page ,我们配置的登录页面是一个 login_page ,但实际上 login_page 并不是一个页面,而是返回一段JSON,这是因为当我未登录就去访问其他页面时Spring Security会自动跳转到到 login_page 页面,但是在Ajax请求中,不需要这种跳转,我要的只是是否登录的提示,所以这里返回json即可。

测试

最后小伙伴可以使用POSTMAN或者RESTClient等工具来测试登录和权限问题,我就不演示了。

Ok,经过上文的介绍,想必小伙伴们对Spring Boot+Spring Security处理Ajax登录请求已经有所了解了,好了,本文就说到这里,有问题欢迎留言讨论

以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!

相关推荐:

在Angular4中输入属性与输出属性(详细教程)

在JS中如何实现ajax和同源策略

在JS中如何实现ajax与ajax的跨域请求

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal