Security前后端分离自定义登录详解

Security前后端分离自定义登录

思路:

通过自定义接口去处理前端的登录请求,其主要问题就是如何实现登录的认证!我们需要手动调用security的认证,并且在security配置类中需要定义一些东西

解决:

security配置类(我这里只是测试,根据个人使用更改即可):

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        // 查询用户信息
        provider.setUserDetailsService(userDetailsService());
        // 设置密码加密算法
        provider.setPasswordEncoder(passwordEncoder());
        auth.authenticationProvider(provider);
    }

    @Bean
    @Override
    //构建用户(可以从数据库查找、内存模拟,这里为了方便就使用内存构建)
    protected UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("zhangsan").password("123456").authorities("p1").build());
        manager.createUser(User.withUsername("lisi").password("123456").authorities("p2").build());
        return manager;
    }
    @Bean
    //生成密码编码类,像MD5,这里使用的这个是直接比较String
    public PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    //security配置,核心
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                //访问/test/1,需要p1权限
                .antMatchers("/test/1").hasAnyAuthority("p1")
                //访问/test/2,需要p2权限
                .antMatchers("/test/2").hasAnyAuthority("p2")
                //test下的请求都必须认证(登录)后才能访问
                .antMatchers("/test/**").authenticated()
                //无条件放行test/** 以外的请求
                .anyRequest().permitAll();
        
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }
}

测试接口:

@RestController
public class TestController {
    @Resource
    AuthenticationManager authenticationManager;
    
    @GetMapping("/mylogin")
    public String test(String username, String password){
        //生成验证令牌
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username,password);
        //获取验证完成的认证信息
        Authentication authenticate = authenticationManager.authenticate(token);
        //保存到security上下文中
        SecurityContextHolder.getContext().setAuthentication(authenticate);
        return "登录成功";
    }

    @GetMapping("/test/1")
    public String test1(){
        return "有p1权限,认证后才能看到这条消息";
    }

    @GetMapping("/test/2")
    public String test2(){
        return "有p2权限,认证后才能看到这条消息";
    }
}

详解:

1.配置类中此方法

image-20211114213750121

为什么需要使用DaoAuthenticationProvider,使用为我们使用用户名密码是使用UsernamePasswordAuthenticationToken生成的验证令牌(登录接口中也是这样写的),而UsernamePasswordAuthenticationToken类型的验证令牌最终会匹配到DaoAuthenticationProvider来进行验证(因为只有他能验证这种类型的)。

我们需要给这个验证提供者(DaoAuthenticationProvider)提供用户信息和密码使用什么加密算法。

提供用户信息即图中:setUserDetailsService(),其中的userDetailsService()方法是你在配置类中定义的,里面你可以自定定义逻辑,比如最常用的数据库查询用户信息

加密算法即图中:setPasswordEncoder(),其中的passwordEncoder()方法也是在配置类中定义的,稍后会讲解那一块

2.配置类的userDetailsService()方法

image-20211114220730298

这里是在内存中创建的数据,要查数据库怎么玩??(见名知意它是个service,service掉dao就能拿到用户信息了)

@Override
    @Bean
    protected UserDetailsService userDetailsService() {
        return new UserDetailsServiceImpl();
    }

你需要写一个类实现UserDetailsService,实现其方法loadUserByUsername(String username)即可,在这个方法里面你就可以调用dao去查这个用户,并将其返回(返回类型是userdetails),所以用户实体类必须继承User类:如下介绍

是Security中的User类

import org.springframework.security.core.userdetails.User;

这个security中的User,该User实现了UserDetails,所以我们继承即可。继承之后我们只需要创建两个

构造函数即可,它有错误提示,点一下就帮我们创建好了:

public class MyUser extends User {
    private String userName;
    private String password;

    public MyUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);
    }

    public MyUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
    }
}

3. 密码加密方式

image-20211114224838212

其作用是选择一个密码加密方式,就是在做密码比对的时候,用什么加密方式去加密前端传来的密码,然后再和数据库中的密码比较!

4.注入AuthenticionManager

image-20211114225130043

就是注入一个认证管理器,非要说些什么的话,就说说它是个什么吧。

AuthenticionManager是一个接口,看它的意思就知道它是管理认证的,该接口中也只有一个认证方法:image-20211114225637875

看看它的实现类image-20211114225713726

其实现类ProviderManager(它是做认证的,delegator结尾的是授权的。我也不敢确定说对没有。。)就是我们用到的,它会帮我们找到能够验证的认证提供类(即DaoAuthenticionProvider)

5.我的登录接口为啥用的get请求方式

不知道你们注意到我的登录接口是get请求没有,是这样的:

因为我并没有写前端,我之前使用的post方式,我图方便就在postman上测的,能够登录成功也确实认证成功,但是,并不能访问需要身份认证后才能访问的请求,我明明已经认证成功了呀?

是因为,认证成功之后,你的认证信息会被放到session中

image-20211114231719283

如图,这是security初始化内部实例时(springboot启动的时候),使用的就是httpsession,你要问在哪用到了这个repo:

image-20211114231921030

我们只需要在接口中,将认证信息保存到security上下文即可,后续就交给security处理:

image-20211114232822236

所以,你必须在同一个浏览器中先登录认证才能有这个session,也就才能访问得到你有权访问的请求(当然你也可以在postman中加上这个session,不过就麻烦很多,我使用get效果一样的)

image-20211114233234091

最后这里有一篇不错的关于security前后端分离登录的文章:https://blog.csdn.net/ycf921244819/article/details/108538506

这里是我分析的security认证过程源码(有点乱,但最后两张图比较清晰),有兴趣的可以来看看:

https://blog.csdn.net/qq_42682745/article/details/120713818

Guess you like

Origin blog.csdn.net/qq_42682745/article/details/121326021