Spring Security是什么? - 简单例子(三)

本文详细介绍Spring Security的安全配置步骤,包括安全配置的实现、认证校验、Remember Me功能配置、授权配置等,并提供具体代码示例。

2、spring security中,安全配置通过继承WebSecurityConfigurerAdapter来配置

@Configuration
public class MyWebSecurityConfigurerAdapter  extends WebSecurityConfigurerAdapter{
    protected void configure(HttpSecurity http) throws Exception {
    	//做大量的配置
    	//认证配置
	//授权配置
// 例如:
                http
                .authorizeRequests()
                //访问"/"和"/home"路径的请求都允许
                .antMatchers("/", "/home","/staff","/staff/*")
                .permitAll()
                //而其他的请求都需要认证
                .anyRequest()
                .authenticated()
                .and()
                //修改Spring Security默认的登陆界面
                .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
                .logout()
                .permitAll();

    }

3、添加认证校验 (authentication)

  • 创建数据库,创建一个用户库,用户名和密码

    在这里插入图片描述

  • 创建访问数据库的接口

    public interface UserMapper {
        User selectByUserName(String userName);
    
        List<String> selectAllRoleByUserId(Integer userId);
    
        List<String> selectPermissionsByUserId(Integer userId);
    
    }
    
    
  • 创建访问数据库的userMapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.demo.mapper.UserMapper">
        <select id="selectByUserName" resultType="com.demo.entity.User">
            select id,userName,password
            from t_user where username = #{userName}
        </select>
        <select id="selectAllRoleByUserId" resultType="String">
            select r.name as name from t_role_user u,t_role r where r.id = u.rid and u.uid=#{userId};
        </select>
        <select id="selectPermissionsByUserId" resultType="String">
            SELECT permission FROM t_role r,t_role_user u,t_role_menu rm,t_menu m
            where r.id = u.uid and rm.mid = m.id and u.rid =rm.rid and u.uid=#{userId}
        </select>
    </mapper>
    
    
  • 新建一个类,实现spring security提供的接口org.springframework.security.core.userdetails.UserDetailsService

    这样就会调用该类的方法去访问数据库认证了。这一步中会返回一个User对象,该对象是SpringSecurity里定义的User对象

    需要将用户的权限和角色用逗号分割后组装在一起。

    @Service
    public class UserDetailsServiceImpl implements UserDetailsService {
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Autowired
        private UserMapper userMapper;
    
        @Override
        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    
            com.demo.entity.User user = userMapper.selectByUserName(s);
            if (user == null) {
                throw new UsernameNotFoundException("用户不存在");
            }
    
            List<String> roles = userMapper.selectAllRoleByUserId(user.getId());
            List<String> permissions = userMapper.selectPermissionsByUserId(user.getId());
    
            StringBuilder sb = new StringBuilder();
            for (String role : roles) {
                sb.append("ROLE_" + role + ",");
            }
            for (String permission : permissions) {
                sb.append(permission + ",");
            }
    
            String rolepermission = sb.substring(0, sb.length() - 1);
    
            UserDetails userDetails = new User(user.getUserName(), user.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(rolepermission));
            return userDetails;
        }
    }
    
    
  • 注意,

    • 认证的步骤,是先获取用户信息,再校验用户密码的认证过程。

    • 在上步骤中需要使用到一个passwordEncoder编码器,来生成加密密码明文生成密文。

  • 通过java类来配置一个passwordEncoder

    @Configuration
    public class SecurityConfig {
        @Bean
        protected PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    }
    
    

4、添加配置,用于完成认证流程管理

  • 登录的界面:/showLogin

  • 登录逻辑入口 /login

  • 登录成功地址 /showMain

  • 登录失败地址 /showFail

  • .usernameParameter("myusername")以及.passwordParameter("mypassword")定义了登录页面中的表单名称是myusernamemypassword

UsernamePasswordAuthenticationFilter :对/login 的 POST 请求做拦截,校验表单中用户名,密码。

通过源码不难发现:一旦我们使用SpringSecurity来作为认证框架,我们要写自己的登录的接口的话,一定要用post请求,并且用户名和密码是固定的,只能用username和password来作为参数名,因为这是SpringSecurity默认的(如果执意要改,可以通过配置文件进行改)。

public class MyWebSecurityConfigurerAdapter  extends WebSecurityConfigurerAdapter{
    protected void configure(HttpSecurity http) throws Exception {
		
	       http.formLogin()
                //未登录时的地址
                .loginPage("/showLogin")
                //处理登录请求的url
                .loginProcessingUrl("/login")
                //.successForwardUrl("/showMain")
                //认证成功后跳转的地址
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                        httpServletResponse.sendRedirect("/showMain");
                    }
                })
                //.failureForwardUrl("/showFail")
                //登录失败后的处理器
                .usernameParameter("myusername")
                //客户端的密码参数名称
                .passwordParameter("mypassword")
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                        httpServletResponse.sendRedirect("/showFail");
                    }
                });

                    http.exceptionHandling()
                //.accessDeniedHandler(accessDeniedHandler);
                //只适用于非前端框架,适用于同步请求的方式
                //如果是异步请求需要使用上一种方式。
                .accessDeniedPage("/showAccessDenied");

          }
}

5、异常配置

配置异常页面,如果程序异常测跳转到该页面。

如果是异步请求,如ajax,则可以实现AccessDeniedHandler 接口

 @Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.setStatus(httpServletResponse.SC_FORBIDDEN);
        PrintWriter writer = httpServletResponse.getWriter();
        writer.println("权限不足");
        writer.flush();
        writer.close();
    }
}

6、配置 Remember Me

需要配置生成一个token,并将token下发给浏览器。
因此需要一个PersistentTokenRepository类。
通过java配置类的方式来定义bean,这个bean就是persistentTokenRepository

@Configuration
public class RememberMeConfig {
    @Autowired
    private DataSource dataSource;

    @Bean
    protected PersistentTokenRepository getPersistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        //jdbcTokenRepository.setCreateTableOnStartup(true);
        jdbcTokenRepository.setDataSource(dataSource);
        return jdbcTokenRepository;
    }
}
// 在MyWebSecurityConfigurerAdapter中配置.rememberMe()
    private MyAccessDeniedHandler accessDeniedHandler;
    private PersistentTokenRepository persistentTokenRepository;
    protected void configure(HttpSecurity http) throws Exception {
        //其他配置
        http.rememberMe()
            .userDetailsService(userDetailsService)
            .tokenRepository(persistentTokenRepository)
            .tokenValiditySeconds(10*2);
    	//其他配置
    }

7、配置授权

授权一般需要依托用户的角色,对权限集合进行绑定。

  • 因为必须先登录,因此对于登录页面直接方行,antMatchers(“/showLogin”,“/showFail”).access(“permitAll”)。

  • 因为静态资源也要直接放行,所以对于.antMatchers(“/images/**”).permitAll() .regexMatchers(“/js/.*”).permitAll() .antMatchers(“/demo”).permitAll()这几个直接放行。

  • .antMatchers(“/abc”).denyAll() 所有到/abc的请求都拒绝。

  • 最后一行的.anyRequest().authenticated(); 声明所有的请求都需要登录。

public class MyWebSecurityConfigurerAdapter  extends WebSecurityConfigurerAdapter{
    protected void configure(HttpSecurity http) throws Exception {
				
				//前面的认证配置
				
		http.authorizeRequests()
                //.antMatchers("/showLogin","/showFail").permitAll()
                .antMatchers("/showLogin","/showFail").access("permitAll")
                //对于静态和动态请求需要分开
                //.antMatchers("/js/**").permitAll()
                .antMatchers("/abc").denyAll()
                .antMatchers("/jczl").hasAnyAuthority("demo:update")
                //.antMatchers("/jczl").hasAnyRole("ADMIN")
                .antMatchers("/admin").access("@myServiceImpl.hasPermission(request,authentication)")
                .antMatchers("/ip").hasIpAddress("192.168.7.86")
                .antMatchers("/images/**").permitAll()
                .regexMatchers("/js/.*").permitAll()
                .antMatchers("/demo").permitAll()
                .anyRequest().authenticated();

8、配置安全 csrf

为防止跨越的表单提交,在login.html 的隐藏域中需要加上

<input type=“hidden” th:value=“${_csrf.token}” name=“_csrf” th:if=“${_csrf}”>

然后在MyWebSecurityConfigurerAdapter 中继续取消 http.csrf().disable();的注释,即默认开启csrf的校验了。

WebSecurityConfigurerAdapter类:可以通过重载该类的三个configure()方法来制定Web安全的细节。

1、configure(WebSecurity):通过重载该方法,可配置Spring Security的Filter链。

2、configure(HttpSecurity):通过重载该方法,可配置如何通过拦截器保护请求。

3、configure(AuthenticationManagerBuilder):通过重载该方法,可配置user-detail(用户详细信息)服务。

  • 1、使用基于内存的用户存储:

  • 2、基于数据库表进行认证:

\*\* 3、基于LDAP进行认证 \*\*
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 自定义用户认证逻辑
     */
    @Autowired
    private UserDetailsService userDetailsService;


    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 身份认证接口  从写了这个接口,这里最重要的就是WebSecurityConfigurerAdapter接口下的configure方法,这个方法就是我们要实现的认证逻辑。其实也可以不重写configure方法,
        他默认就会去容器里面找PasswordEncoder实现类来作为认证的时候 密码加密 和数据库比较,以及userDetailsService实现类。
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		// 告诉SpringSecurity 我们要使用自己定义的userDetailsService来通过username来获取主体,并且使用了BCryptPasswordEncoder加密进行密码比较
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值