springboot2.x+springsecurity实现权限管理并且获取异常信息显示给前端(不同风格的超详细讲解)

本文为原创文章,风格与你们见的不一样,那是因为,我喜欢将原理和注意事项写到代码的旁边,而不是你们所见的在引用中稍微讲解下就开始看代码,这样很容易迷糊和犯困,而且这样你们才能将代码和原理一一对应起来。此外创作不易,请多多支持和关注。

<!--pom.xml所需要的jar包-->
<!--security安全框架-->
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
</dependency>

首先需要继承安全适配器即WebSecurityConfigurerAdapter的类,并且将实现的各种类统一注入到该类中**(实现的功能注意注释)**

@Slf4j
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
        jsr250Enabled = true, //JSR-250注解
        prePostEnabled = true, //spring表达式注解
        securedEnabled = true //SpringSecurity注解,推荐使用
)//开启注解控制权限
public class AdminWebConfig extends WebSecurityConfigurerAdapter {
    private ObjectMapper objectMapper = new ObjectMapper();
    @Resource  //(该类的作用下面会讲)
    UserDetailsService service;
    @Resource
    private PasswordEncoder passwordEncoder;
    @Bean
    public PasswordEncoder passwordEncoder(){
        // 使用BCrypt加密密码(根据业务要求,使用该方法)
        return new BCryptPasswordEncoder();
    }
    //防止无法被 Spring 容器感知到,进而导致当用户注销登录之后导致用户无法重新登录进来
    @Bean
    HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }

  //静态资源设置
    @Override
    public void configure(WebSecurity webSecurity) {
        //不拦截静态资源,所有用户均可访问的资源
        webSecurity.ignoring().antMatchers("/css/**","/img/**","/js/**","/expression/**,/face/**");
    }
    //HTTP请求安全处理(拦截请求)
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //链式编程
        http.authorizeRequests()
        //为了防止你们弄混登录页面的url和执行登录过程的url,我特意区分开来的,看下面的代码               .antMatchers("/","/toLogin","/login").permitAll()//允许通过的url
                .antMatchers("/vip/**").hasAuthority("vip")//url需要相关的权限才能通过
                .antMatchers("/admin/**").hasAuthority("admin")
                .anyRequest().authenticated()//对于任意的请求会拦截,除了开放的以外
                .and().exceptionHandling().accessDeniedPage("/forbidden");//没有权限执行的url,将跳转到该页面去

		//这里我需要声明的是,对于登录过程它会去执行哪个方法,就是需要实现AuthenticationProvider类,在下面会讲,请耐心看注释。
        http.formLogin()
        .loginProcessingUrl("/toLogin") //指定自定义form表单请求的路径(这里有个大坑,form表单中不能写action="***",只能写th:action="@{/toLogin}",th就是thymeleaf模板引擎的语法格式;不然你永远都不会登录成功)
        .loginPage("/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 {
                if (e instanceof SessionAuthenticationException){
                    request.setAttribute("error","已经登录过了,请不要重新登录");
                    request.getRequestDispatcher("/login").forward(request,response);
                    return;
                }
                request.setAttribute("error",e.getMessage());
                request.getRequestDispatcher("/login").forward(request,response);
            }
        });
        //.failureUrl("/undefinedError") //失败返回的url
        http.csrf().disable(); //关闭csrf防护

        // 禁用缓存
        http.headers().cacheControl();

        http.logout()
            .logoutUrl("/logout")
            .logoutSuccessHandler(new LogoutSuccessHandler(){
                @Override
                public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                    response.sendRedirect("/toLogin");
                }
            });

        //单用户登录,如果有一个登录了,同一个用户在其他地方登录将前一个剔除下线
        http.sessionManagement().maximumSessions(1)
                .maxSessionsPreventsLogin(true)
                .expiredSessionStrategy(new SessionInformationExpiredStrategy() {
            @Override
            public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
                Map<String, Object> map = new HashMap<>(16);
                map.put("code", 0);
                map.put("msg", "已经另一台机器登录,您被迫下线。" + event.getSessionInformation().getLastRequest());
                // Map -> Json
                String json = objectMapper.writeValueAsString(map);

                event.getResponse().setContentType("application/json;charset=UTF-8");
                event.getResponse().getWriter().write(json);
            }
        }).expiredUrl("/toLogin");

        //单用户登录,如果有一个登录了,同一个用户在其他地方不能登录(自定义业务就不在这里写了,同上)
        http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true);
    }
		//记住功能
        http.rememberMe()
                .rememberMeParameter("remeberme")//默认为remember-me,但我改成了remeberme,前端可以通过input的name="remeberme"拿到
                .key("123")//指定Token识别字段
                .tokenValiditySeconds(24*60*60)//remeber-me的cookie默认2周(14天)
                .userDetailsService(service) //指定remember-me 功能自动登录过程使用的 UserDetailsService 对象(即下面即将讲的CustomUserDetailsService对象注入进来即可)
                .authenticationSuccessHandler(new AuthenticationSuccessHandler(){

                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                        //这里面可以写自定义的业务,然后返回给前端  
                    }
            });
}

继承安全适配器类过后,所以实现AuthenticationProvider 类才会起作用,而实现AuthenticationProvider 类的作用就是后端会将前端的信息拿到这里来判断。(判断过程注意注释)

//自定义认证规则
@Slf4j
@Component
public class VAuthenticationProvider implements AuthenticationProvider {
    @Resource
    CustomUserDetailsService service;
    @Resource
    HttpSession session;
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //是否认证过
        if (!authentication.isAuthenticated()){
            String username=authentication.getName();
            String password=authentication.getCredentials().toString();
            UserDetails 
            //这里需要实现UserDetailsService类,该类的作用是将前端的信息拿到数据库中去判断userDetails=service.loadUserByUsername(username);
            if (userDetails==null){
                log.info("not find user");
                throw new BadCredentialsException("用户没有找到");
            }
            if (!password.equals(userDetails.getPassword())) {
                log.info("pwd is error");
                throw new BadCredentialsException("密码错误");
            }
            session.setAttribute("loginUser",authentication.getPrincipal());
            //认证校验通过后,封装UsernamePasswordAuthenticationToken返回
            return new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
        }
        return null;
    }

    //当前的AuthenticationProvider支持哪种类型认证
    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.equals(authentication);
    }
    
}

UserDetailsService的作用是在实现AuthenticationProvider的authenticate()方法时,需要将前端的数据拿到该类的loadUserByUsername()中去判断数据库中去判断是否存在此人信息(判断过程注意注释)

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Resource
    PowerMapper powerMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 通过用户名从数据库获取用户信息(这里我就不粘贴了吧,就是基本的查询,建议角色表和用户表分开做,符合数据库的设计规则)
        VipUser user = powerMapper.showPower(username);

        // 角色集合
        List<GrantedAuthority> authorities = new ArrayList<>();
        if (user==null){
            return null;
        }
        //将获取到的user.getRName()即角色权限放入authorities 中,最后作为其中一个参数返回
        authorities.add(new SimpleGrantedAuthority(user.getRName()));

        return new User(
                user.getRName(),
                user.getVPassword(),
                authorities
        );
    }
}

本文你会发现对于在开头的WebSecurityConfigurerAdapter类中,所有的异常信息处理所实现的handler方法都写在链式编程里面的,会感觉很多,容易晕,所以建议你们分开写在不同的类里面,然后统一注入到WebSecurityConfigurerAdapter类中。

后期我也将会出一篇关于springboot2.x+springsecurity+redis+jwt+自定义验证码实现登录功能(登录功能为超时刷新和失效处理)
由于这个太多了,需要的私信我

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值