欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Security学习笔记

程序员文章站 2022-07-12 16:07:35
...

Spring Security是基于JavaEE标准中的Filter实现的。

Spring Security的源码是Spring全家桶中最复杂的之一。推荐学习网站www.spring4all.com

Spring Security包括认证授权部分。

使用方法

导入依赖包后Spring Security直接接管了整个系统的认证,自带了登录页面和自动生成了一个账号密码。

需要修改三个地方配置Spring Security:

  1. User对象实现UserDetails接口;
  2. UserService实现UserDetailsService接口;
  3. 新建一个配置类(带@Configuration注解)继承WebSecurityConfigurerAdapter类,重写三个参数不同的configure方法。

1.User对象实现UserDetails接口

主要是实现getAuthorities方法返回User的权限标识。

关于User权限标识的手段,复杂的有用户表+角色表+权限表;简单的使用加个type成员标记即可。

public class User implements UserDetails {
    private int id;
    private String username;
    private String password;
    private String salt;
    private String email;
    private int type;
    private int status;
    private String activationCode;
    private String headerUrl;
    private Date createTime;

  //省略Getter和Setter

    @Override
    public boolean isAccountNonExpired() {
        return true;// true: 账号未过期.
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;// true: 账号未锁定
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;//true:凭证未过期
    }

    @Override
    public boolean isEnabled() {
        return true;//true:账号可用
    }

    //获取权限标识
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> list = new ArrayList<>();
        list.add((GrantedAuthority) () -> {
            switch (type){
                case 1:return "ADMIN";
                case 2:return "MODERATOR";
                default:return "USER";
            }
        });
        return list;
    }

}

2.UserService实现UserDetailsService接口

实现loadUserByUsername方法即可,非常简单。

@Service
public class UserService implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;
    public User findUserByName(String username) {
        return userMapper.selectByName(username);
    }
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return this.findUserByName(username);
    }
}

3.新建一个配置类继承WebSecurityConfigurerAdapter类,重写三个configure方法

这里涉及较多Spring Security的组件概念,比较容易搞乱。

三个configure方法:

  • configure(WebSecurity web)用于配置不过滤静态资源访问;
  • configure(AuthenticationManagerBuilder auth)实现自定义的认证逻辑;
  • configure(HttpSecurity http)定义http请求的认证行为,定义授权,remember-me和增加自定义Filter。

/**
 * 配置Spring Security,这里涉及较多组件不要搞乱
 *
 * @author cuiwj
 * @date 2020/3/31
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserService userService;

    @Override
    public void configure(WebSecurity web) throws Exception {
        // 不过滤静态资源的访问
        web.ignoring().antMatchers("/resources/**");
    }

    // AuthenticationManager: 认证的核心接口.
    // AuthenticationManagerBuilder: 用于构建AuthenticationManager对象的工具.
    // ProviderManager: AuthenticationManager接口的默认实现类.
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 内置的认证规则
//         auth.userDetailsService(userService).passwordEncoder(new Pbkdf2PasswordEncoder("12ab"));
        //本系统已使用md5(password1+salt)的自定义认证规则

        // AuthenticationProvider: ProviderManager持有一组AuthenticationProvider,每个AuthenticationProvider负责一种认证.
        // 委托模式: ProviderManager将认证委托给AuthenticationProvider.
        auth.authenticationProvider(new AuthenticationProvider() {
            // 这个方法具体实现自定义的认证规则
            // Authentication: 用于封装认证信息的接口,不同的实现类代表不同类型的认证信息.
            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                String username = authentication.getName();
                String password = (String) authentication.getCredentials();
                User user = userService.findUserByName(username);
                if (user == null) {
                    throw new UsernameNotFoundException("账号不存在!");
                }
                password = CommunityUtil.md5(password + user.getSalt());
                if (!user.getPassword().equalsIgnoreCase(password)) {
                    throw new BadCredentialsException("密码不正确!");
                }
                // principal: 主要信息; credentials: 证书; authorities: 权限;
                return new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
            }

            // 当前的AuthenticationProvider支持哪种类型的认证.
            @Override
            public boolean supports(Class<?> aClass) {
                // UsernamePasswordAuthenticationToken: Authentication接口的常用的实现类.
                return UsernamePasswordAuthenticationToken.class.equals(aClass);
            }
        });
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /* 登录相关配置 */
        http.formLogin()
                .loginPage("/loginpage")
                .loginProcessingUrl("/login")
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        //认证成功的行为:重定向到主页
                        response.sendRedirect(request.getContextPath() + "/index");
                    }
                })
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
                        //认证成功的行为:request带上错误提示,转发到登录页面
                        request.setAttribute("error", e.getMessage());
                        request.getRequestDispatcher("/loginpage").forward(request, response);
                    }
                });
        /*退出相关配置*/
        http.logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(new LogoutSuccessHandler() {
                    @Override
                    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        //退出成功的行为:重定向到首页
                        response.sendRedirect(request.getContextPath() + "/index");
                    }
                });
        /*request路径授权配置*/
        http.authorizeRequests()
                .antMatchers("/letter").hasAnyAuthority("ADMIN", "USER")
                .antMatchers("/admin").hasAnyAuthority("ADMIN")
                .and().exceptionHandling().accessDeniedPage("/denied");

        /*记住我相关配置*/
        http.rememberMe()
                .userDetailsService(userService)
                .tokenRepository(new InMemoryTokenRepositoryImpl())//内置的token持久化类,存在内存里;可自定义实现一个
                .tokenValiditySeconds(3600 * 24);

        /*增加自定义Filter实现额外功能*/
        //自定义Filter处理验证码
        http.addFilterBefore(new Filter() {
            @Override
            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
                HttpServletRequest request = (HttpServletRequest) servletRequest;
                HttpServletResponse response = (HttpServletResponse) servletResponse;
                if (request.getServletPath().equals("/login")) {
                    String verifyCode = request.getParameter("verifyCode");
                    if (verifyCode == null || !verifyCode.equalsIgnoreCase("1234")) {//为方便验证码已写死
                        request.setAttribute("error", "验证码错误!");
                        request.getRequestDispatcher("/loginpage").forward(request, response);
                        return;
                    }
                }
                chain.doFilter(request, response);
            }
        }, UsernamePasswordAuthenticationFilter.class);
    }
}
Spring Security认证成功后,认证结果会通过SecurityContextHolder存入SecurityContext中。在程序的其他地方可以通过SecurityContextHolder类获取User对象。如:
 @RequestMapping(path = "/index", method = RequestMethod.GET)
    public String getIndexPage(Model model) {
        // 认证成功后,结果会通过SecurityContextHolder存入SecurityContext中.
        Object obj = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (obj instanceof User) {
            model.addAttribute("loginUser", obj);
        }
        return "/index";
    }

代码链接:https://github.com/Treyoo/springsecuritydemo

 

相关标签: 笔记 spring