spring security认证与配置

1认证

认证:辨别用户是否本系统用户。

优势:1 提供多样式的加密方法

           2 提供多样式的用户存储方式

           3 使用者无需关注验证封装业务 只需要提供查询方法即可

           4 多样式的认证方式

           5 提供用户信息获取方式

可扩展的功能

      1 记住我

      2 邮箱验证

      3 手机验证

      4 验证码验证

配置详解

spring security统一实现WebSecurityConfigurerAdapter接口 按照以下需求添加以下配置 就可以整合成功

@Configuration
@EnableWebSecurity // 开启springsecurity过滤链 filter
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启注解方法级别权限控制
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    //实现输入是明文 存储到数据库为密文 写死即可
    @Bean
    public PasswordEncoder passwordEncoder() {
        // 明文+随机盐值》加密存储
        return new BCryptPasswordEncoder();
    }

    //用户验证的业务流程 也就是查询用户的业务代码
    @Autowired
    UserDetailsService customUserDetailsService;

    /**
     * 认证管理器: 将上文查询用户是否存在的service按样式写入 修改service 其余写死 还有密码写死或存放内存中等方式 这里不讨论
     * 1. 认证信息(用户名,密码)
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 数据库存储的密码必须是加密后的也就是必须要有passwordEncoder,不然会报错:There is no PasswordEncoder mapped for
        auth.userDetailsService(customUserDetailsService);
    }

    //验证码配置
    @Autowired
    private ImageCodeValidateFilter imageCodeValidateFilter;

    //当验证成功后可以返回json或者路径 但是现在基于前后台分离 大多数都是返回json AuthenticationSuccessHandler为成功后转为json的处理 按照本文配置即可
    @Autowired
    private AuthenticationSuccessHandler customAuthenticationSuccessHandler;

    //当验证成功后可以返回json或者路径 但是现在基于前后台分离 大多数都是返回json AuthenticationFailureHandler为失败后转为json的处理 按照本文配置即可
    @Autowired
    private AuthenticationFailureHandler customAuthenticationFailureHandler;

    //建立数据源
    @Autowired
    DataSource dataSource;


    @Autowired
    private InvalidSessionStrategy invalidSessionStrategy;

    /**
     * 当同个用户session数量超过指定值之后 ,会调用这个实现类
     */
    @Autowired
    private SessionInformationExpiredStrategy sessionInformationExpiredStrategy;

    /**
     * 持久化token
     * @return
     */
    @Bean
    public JdbcTokenRepositoryImpl jdbcTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        // 是否启动项目时自动创建表,true自动创建
//        jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }
    /**
     * 核心拦截器 当你认证成功之后 ,springsecurity它会重写向到你上一次请求上
     * 资源权限配置:
     * 1. 被拦截的资源
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //调用验证码过滤器 下文会详细介绍
        http.addFilterBefore(imageCodeValidateFilter, UsernamePasswordAuthenticationFilter.class)
                .formLogin() // 表单登录方式
                .loginPage("/login/page") //登录页的页面地址
                .loginProcessingUrl("/login/form") // 登录表单提交处理url, 默认是/login
                .usernameParameter("username") //默认的是 username
                .passwordParameter("password")  // 默认的是 password
                .successHandler(customAuthenticationSuccessHandler) //登录成功返回的json
                .failureHandler(customAuthenticationFailureHandler) //登录失败返回的json
                .and() //每个类型的配置 以.and()间隔 相当于;
                .authorizeRequests() // 授权请求
                .antMatchers("/login/page",
                        "/code/image","/mobile/page", "/code/mobile",
                        "/code/image",
                        "/code/mobile",
                        "/mobile/page"
                ).permitAll() // 放行/login/page不需要认证可访问 因为如果在调用验证接口时还需要权限 那么就没有入口了 所以一些不需要登录就能访问的接口在此配置

                // 此处是鉴权
                // 有 sys:user 权限的可以访问任意请求方式的/role
                .antMatchers("/user").hasAuthority("sys:user")
                // 有 sys:role 权限的可以访问 get方式的/role
                .antMatchers(HttpMethod.GET,"/role").hasAuthority("sys:role")
                .antMatchers(HttpMethod.GET, "/permission")
                // ADMIN 注意角色会在前面加上前缀 ROLE_ , 也就是完整的是 ROLE_ADMIN, ROLE_ROOT
                .access("hasAuthority('sys:premission') or hasAnyRole('ADMIN', 'ROOT')")
                // 此处是鉴权

                .anyRequest().authenticated() //所有访问该应用的http请求都要通过身份认证才可以访问
                .and()
                .rememberMe() //记住我功能
                 //记住功能配置
                .tokenRepository(jdbcTokenRepository()) //保存token信息
                .tokenValiditySeconds(604800) //记住我有效时长
                .and()
                .sessionManagement()// session管理
                .invalidSessionStrategy(invalidSessionStrategy) //当session失效后的处理类 //.expiredSessionStrategy(sessionInformationExpiredStrategy)// 当用户达到最大session数后,则调用此处的实现
                .maximumSessions(1) // 每个用户在系统中最多可以有多少个session
                .maxSessionsPreventsLogin(true) // 当一个用户达到最大session数,则不允许后面再登录
                .sessionRegistry(sessionRegistry())
                .and().and()
                .logout()//登出相关
                .addLogoutHandler(customLogoutHandler) // 退出清除缓存
                .logoutUrl("/user/logout") // 退出请求路径
                .logoutSuccessUrl("/mobile/page") //退出成功后跳转地址
                .deleteCookies("JSESSIONID") // 退出后删除什么cookie值
        ;// 注意不要少了分号
        http.csrf().disable(); // 关闭跨站请求伪造
    }

    /**
     * 退出清除缓存
     */
    @Autowired
    private CustomLogoutHandler customLogoutHandler;

    /**
     * 为了解决退出重新登录问题
     * @return
     */
    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }
    /**
     * 一般是针对静态资源放行
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web){
        web.ignoring().antMatchers("/js/**", "/css/**");
    }
}

清除缓存方法

@Component
public class CustomLogoutHandler implements LogoutHandler {

    @Autowired
    private SessionRegistry sessionRegistry;

    @Override
    public void logout(HttpServletRequest request,
                       HttpServletResponse response,
                       Authentication authentication) {
        // 退出之后 ,将对应session从缓存中清除 SessionRegistryImpl.principals
        sessionRegistry.removeSessionInformation(request.getSession().getId());
    }
}

编写user验证方法类

需要继承UserDetailsService  为jar包提供

public interface UserDetailsService {
    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

获取SysUser实体方法和获取此人菜单的方法 这里不多说

public abstract class AbstractUserDetailsService implements UserDetailsService {

    @Autowired
    private SysPermissionService sysPermissionService;

    /**
     * 这个方法交给子类去实现它,查询用户信息
     *
     * @param usernameOrMobile 用户名或者手机号
     * @return
     */
    public abstract SysUser findSysUser(String usernameOrMobile);

    @Override
    public UserDetails loadUserByUsername(String usernameOrMobile) throws UsernameNotFoundException {
        // 1. 通过请求的用户名去数据库中查询用户信息
        SysUser sysUser = findSysUser(usernameOrMobile);
        // 通过用户id去获取权限信息
        findSysPermission(sysUser);
        return sysUser;
    }

    private void findSysPermission(SysUser sysUser) {
        if (sysUser == null) {
            throw new UsernameNotFoundException("用户名或密码错误");
        }
        // 2. 查询该用户有哪一些权限
        List<SysPermission> permissions = sysPermissionService.findByUserId(sysUser.getId());
        if (CollectionUtils.isEmpty(permissions)) {
            return;
        }
        // 在左侧菜单 动态渲染会使用,目前先把它都传入
        sysUser.setPermissions(permissions);
        // 3. 封装权限信息
        List<GrantedAuthority> authorities = Lists.newArrayList();
        for (SysPermission sp : permissions) {
            // 权限标识
            String code = sp.getCode();
            authorities.add(new SimpleGrantedAuthority(code));
        }
        sysUser.setAuthorities(authorities);
    }
}
@Component("customUserDetailsService")
public class CustomUserDetailsService extends AbstractUserDetailsService{
    Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired // 不能删掉,不然报错
    PasswordEncoder passwordEncoder;

    @Autowired
    SysUserService sysUserService;

    @Override
    public SysUser findSysUser(String usernameOrMobile) {
        logger.info("请求认证的用户名: " + usernameOrMobile);
        // 1. 通过请求的用户名去数据库中查询用户信息
        return sysUserService.findByUsername(usernameOrMobile);
    }
}
CustomAuthenticationSuccessHandler与CustomAuthenticationFailureHandler 
@Component("customAuthenticationSuccessHandler")
public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Autowired(required = false) // 容器中可以不需要有接口的实现,如果有则自动注入
    AuthenticationSuccessListener authenticationSuccessListener;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
        HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        if(authenticationSuccessListener != null) {
            // 当认证之后 ,调用此监听,进行后续处理,比如加载用户权限菜单
            authenticationSuccessListener.successListener(request, response, authentication);
        }
        if(LoginResponseType.JSON.equals(
                    "post")) {
            // 认证成功后,响应JSON字符串
            MengxueguResult result = MengxueguResult.ok("认证成功");
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(result.toJsonString());
        }else {
            //重定向到上次请求的地址上,引发跳转到认证页面的地址
            logger.info("authentication: " + JSON.toJSONString(authentication));
            super.onAuthenticationSuccess(request, response, authentication);
        }

    }
}
@Component("customAuthenticationFailureHandler")
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    /**
     *
     * @param exception 认证失败时抛出异常
     */
    @Override
    public void onAuthenticationFailure(HttpServletRequest request,
            HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        if(LoginResponseType.JSON.equals("post")) {
            // 认证失败响应JSON字符串,
            MengxueguResult result = MengxueguResult.build(HttpStatus.UNAUTHORIZED.value(), exception.getMessage());
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(result.toJsonString());
        }else {
            // 重写向回认证页面,注意加上 ?error
//            super.setDefaultFailureUrl(securityProperties.getAuthentication().getLoginPage()+"?error");
            // 获取上一次请求路径
            String referer = request.getHeader("Referer");
            logger.info("referer:" + referer);

            // 如果下面有值,则认为是多端登录,直接返回一个登录地址
            Object toAuthentication = request.getAttribute("toAuthentication");
            String lastUrl = toAuthentication != null ? "/login/page"
                    : StringUtils.substringBefore(referer,"?");
            logger.info("上一次请求的路径 :" + lastUrl);
            super.setDefaultFailureUrl(lastUrl+"?error");
            super.onAuthenticationFailure(request, response, exception);
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_20143059/article/details/113623919