Spring Security学习笔记

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

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值