Spring Security简单入门与自定义配置

#

Spring Security简单入门与自定义配置

一、请先学习两个单词

authorization授权
authentication认证

二、Spring Security简介

Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.

Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements
Spring Security所解决的问题就是安全访问控制,而安全访问控制功能其实就是对所有进入系统的请求进行拦截,校验每个请求是否能够访问它所期望的资源。Spring Security对Web资源的保护是靠Filter实现的。

三、配置运行环境

与springboot结合使用,需要额外导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

四、简单的demo

这时我们就简单的配置好了运行环境(不要忘记导入web的相关依赖)
我们启动项目,打开浏览器,访问localhost,发现会自动弹出页面。 springboot进行了自动装配。同时控制台会有输出,当然这个值是随机的

  Using generated security password: dfbed846-28a8-445c-9dbb-badf776d239a

这时我们输入username:root,password为以上即可成功进入

五、自定义登陆逻辑并授权

我们需要实现一个接口UserDetailsService,实现其中的方法loadUserByUsername
示例如下

@Service // 不要忘记交给spring去管理 
public class UserDetailServiceImpl implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
         // 通常需要在数据库查询,便于演示假定其为admin
        if(!"admin".equals(username)){
            throw new UsernameNotFoundException("用户名不存在");
        }
        String password = passwordEncoder.encode("123");

        return new User(username,password,
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

需要注意的是,PasswordEncoder为一个接口,他的实现类的注入需要我们手动去完成,这个类为加密方式,通常采用BCryptPasswordEncoder

@Bean
public PasswordEncoder getPw() {
    return new BCryptPasswordEncoder();
}

以下工具类为用户授权,有两种写法,一种如下直接授予"admin"权限,当然可以自定义(VIP之类的都可),其为List说明可以用逗号分隔赋予多种权限

AuthorityUtils.commaSeparatedStringToAuthorityList("admin"))

第二种为定义角色,必须用ROLE_定义,如ROLE_root。当然也可以用逗号分隔,进行多角色,多权限定义
注意返回值UserDetails为接口,我们也可以定义自己的实现类。但此处采用User实现类,注意不要导错包

六、自定义登录页面

使用自带的登录页面并不美观,并且难以实现更加个性化的内容。首先准备好自己的login.html的页面,将其表单的参数设置为如下

<form action="/login" method="post">
    用户名<input type="text" name="username"><br/>
    密码<input type="password" name="password"><br/>
    <input type="submit" value="登录">
</form>

name的值建议为username和password这样在后面配置的时候就不用手动更改,避免麻烦。同时记住表单提交的action="/login"

然后增加一个配置类去继承WebSecurityConfigurerAdapter,覆盖其中的configuer(HttpSecurity http)方法
参考如下

protected void configure(HttpSecurity http) throws Exception {
        // 表单提交
        http.formLogin()
                // 自定义登录页面
                .loginPage("/login.html")
                // 必须得和login.html中的action一致
                .loginProcessingUrl("/login");
        // 身份认证
        http.authorizeRequests()
                .antMatchers("/login.html").permitAll()
                .anyRequest().authenticated();
        // 关闭csrf防护
        http.csrf().disable();
}

身份认证部分下面在看
这时我们按住ctrl键点击loginPage可以看见上一部分的源码

    public FormLoginConfigurer() {
        super(new UsernamePasswordAuthenticationFilter(), (String)null);
        this.usernameParameter("username");
        this.passwordParameter("password");
    }

发现之所以表单提交时要做命名要做这样的规定是因为对于usernameParameter和passwordParameter做了默认规定,所以我们闲得慌的话也可以对这个自定义,在链中写接着写.usernameParameter()就可实现对其的自定义

七、自定义登陆成功的跳转页面,登录失败的跳转页面

以成功为例,失败的方法与其相同

protected void configure(HttpSecurity http) throws Exception {
        // 表单提交
        http.formLogin()
                // 自定义登录页面
                .loginPage("/login.html")
                // 必须得和login.html中的action一致
                .loginProcessingUrl("/login")
                .successForwardUrl("/toIndex");
        // 授权
        http.authorizeRequests()
                .antMatchers("/login.html").permitAll()
                .anyRequest().authenticated();
        // 关闭csrf防护
        http.csrf().disable();
    }

点进源码看到

public FormLoginConfigurer<H> successForwardUrl(String forwardUrl) {
        this.successHandler(new ForwardAuthenticationSuccessHandler(forwardUrl));
        return this;
    }

实际上是返回了一个successHandler,并且其中的参数为forwardUrl。说明方式为转发。如果我们想搞点坏的,玩一手重定向到www.baidu.com。采用默认的方法就不行,需要我们自己去重写ForwardAuthenticationSuccessHandler的接口AuthenticationSuccessHandler的实现类

public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private String url;

    public MyAuthenticationSuccessHandler(String url) {
        this.url = url;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
                                        HttpServletResponse httpServletResponse,
                                        Authentication authentication) throws IOException {
        httpServletResponse.sendRedirect(url);
    }
}

我们采用重定向的方式。这样的话我们直接在链中通过successHandler来书写

http.formLogin()
                // 自定义登录页面
                .loginPage("/login.html")
                // 必须得和login.html中的action一致
                .loginProcessingUrl("/login")
                .successHandler(new MyAuthenticationSuccessHandler("http://www.baidu.com"));

八、身份认证简单分析

(一)基于代码

http.authorizeRequests()
    .antMatchers("/login.html").permitAll()
    .anyRequest().authenticated();

authorizeRequests()表示请求授权,默认为拦截所有
像一些静态资源如css,js,img等不应该被拦截,且登录页面也不应该不拦截

antMatchers()方法表示ant路径匹配原则,用于匹配需要放行内容
? : 匹配一个字符
* :匹配0个或多个字符
** : 匹配0个或多个目录

如antMatchers("/static/**/*.css")表示匹配static下的css资源
当然也可以使用正则表达式regexMatchers()方法

permitAll()表示允许所有。即该资源允许所有的用户访问。这时候可以控制他的权限(身份认证)如
如:
1.基于权限访问
hasAuthority(“admin”),需要有admin权限才允许访问,hasAnyAuthority(“admin”,“root”)需要有admin或root权限才能访问。而权限的赋予在自定义登录逻辑时使用了AuthorityUtils.commaSeparatedStringToAuthorityList(“admin”)赋予
严格区分大小写
2.基于角色访问
hasRole(“abc”),需要改角色为abc才能访问,注意此时不要在前面添加ROLE_,角色的赋予同样在AuthorityUtils.commaSeparatedStringToAuthorityList(“admin”,“ROLE_abc”)中给出
也同样区分大小写
3.基于IP地址访问
hasIpAddress(“127.0.0.1”),注意localhost,127.0.0.1,ipconfig查出来的不能通用。

这时候点进源码可以看到

public ExpressionUrlAuthorizationConfigurer<H>.ExpressionInterceptUrlRegistry permitAll() {
            return this.access("permitAll");
        }

这些权限的赋予都是返回的一个access表达式。我们同样可以去实现写个access来书写自己的表达式,没什么意思不演示了

而最后的,表示其他请求都需要拦截

.anyRequest().authenticated()

(二)基于注解

首先在主启动类中开启

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

securedEnabled = true表示开启了@Secured。通产写在controller层的方法上,如

@Secured("ROLE_abc")
@RequestMapping("/toIndex")

表示需要时abc这个角色才能访问/toIndex这个URL。这里ROLE_可写可不写。同样的严格区分大小写

prePostEnabled = true表示开启@PreAuthorize,作用同@Secured
如@PreAuthorize(“hasRole(‘abc’)”) = @Secured(“ROLE_abc”),
当然也有hasAuthority,如同hasRole的写法。

九、记住我

同样的在配置类中添加以下链

// 记住我
http.rememberMe()
        // 设置数据源
        .tokenRepository(persistentTokenRepository)
        // 超时时间默认为两周,此处手动设置为60秒便于操作
        .tokenValiditySeconds(60)
        // 自定义登录逻辑
        .userDetailsService(userDetailService);

persistentTokenRepository需要注入
书写其注入配置

@Bean
public PersistentTokenRepository persistentTokenRepository() {
    JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
    jdbcTokenRepository.setDataSource(dataSource);
    // 自动建表,第一次启动时开启,第二次启动时注释
    //jdbcTokenRepository.setCreateTableOnStartup(true);
    return jdbcTokenRepository;
}

这时候要导入数据库相关的驱动jar包,以及在application中去配置相关的数据库配置,并且创建一个名为security的数据库。因为这个功能会在本地自动建表进行持久化保存。
当然也得在login.html中添加以下代码,同样要注意name的值

记住我<input type="checkbox" name="remember-me" value="true">

十、异常处理

异常即所谓权限不够,或身份验证错误,的处理方式。并非账号密码登录错误的跳转页面。比如在上面的登录之后要进行之后的页面跳转时,需要admin权限,却没有就会执行该操作

http.exceptionHandling()
        .accessDeniedHandler(myAccessDeniedHandler);

要自己实现MyAccessDeniedHandler,当然这里采用spring注入的方式

@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        // 设置响应状态403,为权限不足
        httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
        // 返回json
        httpServletResponse.setHeader("Content-Type","application/json;charset=utf-8");
        PrintWriter writer = httpServletResponse.getWriter();
        writer.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员\"}");
        writer.flush();
        writer.close();
    }
}

十一、退出登录

http.logout()
    .logoutSuccessUrl("/login.html");
<a href="/logout">退出</a>

会清除本地的session等。同样得注意href的值

十二、csrf

默认为开启状态
Cross-site request forgery跨域请求伪造
csrf是为了保证不是其他第三方网站访问。要求访问是携带参数名为_csrf的值为token(在服务端产生的)的内容。如果token和服务端产生的token匹配成功,则正常访问
开启后只需要在html中添加

<input type = "hidden" name="_csrf" value=_csrf.token if=_csrf>
  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值