如何使用SpringSecurity保护程序安全
首先,引入依赖:
<dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-security</artifactid> </dependency>
引入此依赖之后,你的web程序将拥有以下功能:
- 所有请求路径都需要认证
- 不需要特定的角色和权限
- 没有登录页面,使用http基本身份认证
- 只有一个用户,名称为user
配置springsecurity
springsecurity配置项,最好保存在一个单独的配置类中:
@configuration @enablewebsecurity public class securityconfig extends websecurityconfigureradapter { }
配置用户认证方式
首先,要解决的就是用户注册,保存用户的信息。springsecurity提供四种存储用户的方式:
- 基于内存(生产肯定不使用)
- 基于jdbc
- 基于ldap
- 用户自定义(最常用)
使用其中任意一种方式,需要覆盖configure(authenticationmanagerbuilder auth)方法:
@configuration @enablewebsecurity public class securityconfig extends websecurityconfigureradapter { @override protected void configure(authenticationmanagerbuilder auth) throws exception { } }
1.基于内存
@override protected void configure(authenticationmanagerbuilder auth) throws exception { auth.inmemoryauthentication() .withuser("zhangsan").password("123").authorities("role_user") .and() .withuser("lisi").password("456").authorities("role_user"); }
2.基于jdbc
@autowired datasource datasource; @override protected void configure(authenticationmanagerbuilder auth) throws exception { auth.jdbcauthentication() .datasource(datasource); }
基于jdbc的方式,你必须有一些特定表表,而且字段满足其查询规则:
public static final string def_users_by_username_query = "select username,password,enabled " + "from users " + "where username = ?"; public static final string def_authorities_by_username_query = "select username,authority " + "from authorities " + "where username = ?"; public static final string def_group_authorities_by_username_query = "select g.id, g.group_name, ga.authority " + "from groups g, group_members gm, group_authorities ga " + "where gm.username = ? " + "and g.id = ga.group_id " + "and g.id = gm.group_id";
当然,你可以对这些语句进行一下修改:
@override protected void configure(authenticationmanagerbuilder auth) throws exception { auth.jdbcauthentication().datasource(datasource) .usersbyusernamequery("select username, password, enabled from users " + "where username=?") .authoritiesbyusernamequery("select username, authority from userauthorities " + "where username=?");
这有一个问题,你数据库中的密码可能是一种加密方式加密过的,而用户传递的是明文,比较的时候需要进行加密处理,springsecurity也提供了相应的功能:
@override protected void configure(authenticationmanagerbuilder auth) throws exception { auth.jdbcauthentication().datasource(datasource) .usersbyusernamequery("select username, password, enabled from users " + "where username=?") .authoritiesbyusernamequery("select username, authority from userauthorities " + "where username=?") .passwordencoder(new standardpasswordencoder("53cr3t");
passwordencoder方法传递的是passwordencoder接口的实现,其默认提供了一些实现,如果都不满足,你可以实现这个接口:
- bcryptpasswordencoder
- nooppasswordencoder
- pbkdf2passwordencoder
- scryptpasswordencoder
- standardpasswordencoder(sha-256)
3.基于ldap
@override protected void configure(authenticationmanagerbuilder auth) throws exception { auth.ldapauthentication() .usersearchbase("ou=people") .usersearchfilter("(uid={0})") .groupsearchbase("ou=groups") .groupsearchfilter("member={0}") .passwordcompare() .passwordencoder(new bcryptpasswordencoder()) .passwordattribute("passcode") .contextsource() .root("dc=tacocloud,dc=com") .ldif("classpath:users.ldif");
4.用户自定义方式(最常用)
首先,你需要一个用户实体类,它实现userdetails接口,实现这个接口的目的是为框架提供更多的信息,你可以把它看作框架使用的实体类:
@data public class user implements userdetails { private long id; private string username; private string password; private string fullname; private string city; private string phonenumber; @override public collection<? extends grantedauthority> getauthorities() { return null; } @override public boolean isaccountnonexpired() { return false; } @override public boolean isaccountnonlocked() { return false; } @override public boolean iscredentialsnonexpired() { return false; } @override public boolean isenabled() { return false; } }
有了实体类,你还需要service逻辑层,springsecurity提供了userdetailsservice接口,见名知意,你只要通过loaduserbyusername返回一个userdetails对象就成,无论是基于文件、基于数据库、还是基于ldap,剩下的对比判断交个框架完成:
@service public class userservice implements userdetailsservice { @override public userdetails loaduserbyusername(string s) throws usernamenotfoundexception { return null; } }
最后,进行应用:
@autowired private userdetailsservice userdetailsservice; @bean public passwordencoder encoder() { return new standardpasswordencoder("53cr3t"); } @override protected void configure(authenticationmanagerbuilder auth) throws exception { auth.userdetailsservice(userdetailsservice) .passwordencoder(encoder()); }
配置认证路径
知道了如何认证,但现在有几个问题,比如,用户登录页面就不需要认证,可以用configure(httpsecurity http)对认证路径进行配置:
@override protected void configure(httpsecurity http) throws exception { }
你可以通过这个方法,实现以下功能:
- 在提供接口服务前,判断请求必须满足某些条件
- 配置登录页面
- 允许用户注销登录
- 跨站点伪造请求防护
1.保护请求
@override protected void configure(httpsecurity http) throws exception { http.authorizerequests() .antmatchers("/design", "/orders").hasrole("role_user") .antmatchers(“/”, "/**").permitall(); }
要注意其顺序,除了hasrole和permitall还有其它访问认证方法:
方法 | 作用 |
---|---|
access(string) | 如果给定的spel表达式的计算结果为true,则允许访问 |
anonymous() | 允许访问匿名用户 |
authenticated() | 允许访问经过身份验证的用户 |
denyall() | 无条件拒绝访问 |
fullyauthenticated() | 如果用户完全通过身份验证,则允许访问 |
hasanyauthority(string...) | 如果用户具有任何给定权限,则允许访问 |
hasanyrole(string...) | 如果用户具有任何给定角色,则允许访问 |
hasauthority(string) | 如果用户具有给定权限,则允许访问 |
hasipaddress(string) | 如果请求来自给定的ip地址,则允许访问 |
hasrole(string) | 如果用户具有给定角色,则允许访问 |
not() | 否定任何其他访问方法的影响 |
permitall() | 允许无条件访问 |
rememberme() | 允许通过remember-me进行身份验证的用户访问 |
大部分方法是为特定方式准备的,但是access(string)可以使用spel进一些特殊的设置,但其中很大一部分也和上面的方法相同:
表达式 | 作用 |
---|---|
authentication | 用户的身份验证对象 |
denyall | 始终评估为false |
hasanyrole(list of roles) | 如果用户具有任何给定角色,则为true |
hasrole(role) | 如果用户具有给定角色,则为true |
hasipaddress(ip address) | 如果请求来自给定的ip地址,则为true |
isanonymous() | 如果用户是匿名用户,则为true |
isauthenticated() | 如果用户已通过身份验证,则为true |
isfullyauthenticated() | 如果用户已完全通过身份验证,则为true(未通过remember-me进行身份验证) |
isrememberme() | 如果用户通过remember-me进行身份验证,则为true |
permitall | 始终评估为true |
principal | 用户的主要对象 |
示例:
@override protected void configure(httpsecurity http) throws exception { http.authorizerequests() .antmatchers("/design", "/orders").access("hasrole('role_user')") .antmatchers(“/”, "/**").access("permitall"); }
@override protected void configure(httpsecurity http) throws exception { http.authorizerequests() .antmatchers("/design", "/orders").access("hasrole('role_user') && " + "t(java.util.calendar).getinstance().get("+"t(java.util.calendar).day_of_week) == " + "t(java.util.calendar).tuesday") .antmatchers(“/”, "/**").access("permitall"); }
2.配置登录页面
@override protected void configure(httpsecurity http) throws exception { http.authorizerequests() .antmatchers("/design", "/orders").access("hasrole('role_user')") .antmatchers(“/”, "/**").access("permitall") .and() .formlogin() .loginpage("/login"); } // 增加视图处理器 @overridepublic void addviewcontrollers(viewcontrollerregistry registry) { registry.addviewcontroller("/").setviewname("home"); registry.addviewcontroller("/login"); }
默认情况下,希望传递的是username和password,当然你可以修改:
.and() .formlogin() .loginpage("/login") .loginprocessingurl("/authenticate") .usernameparameter("user") .passwordparameter("pwd")
也可修改默认登录成功的页面:
.and() .formlogin() .loginpage("/login") .defaultsuccessurl("/design")
3.配置登出
.and() .logout() .logoutsuccessurl("/")
4.csrf攻击
springsecurity默认开启了防止csrf攻击,你只需要在传递的时候加上:
<input type="hidden" name="_csrf" th:value="${_csrf.token}"/>
当然,你也可以关闭,但是不建议这样做:
.and() .csrf() .disable()
知道用户是谁
仅仅控制用户登录有时候是不够的,你可能还想在程序的其它地方获取已经登录的用户信息,有几种方式可以做到:
- 将principal对象注入控制器方法
- 将authentication对象注入控制器方法
- 使用securitycontextholder获取安全上下文
- 使用@authenticationprincipal注解方法
1.将principal对象注入控制器方法
@postmappingpublic string processorder(@valid order order, errors errors,sessionstatus sessionstatus,principal principal) { ... user user = userrepository.findbyusername(principal.getname()); order.setuser(user); ... }
2.将authentication对象注入控制器方法
@postmappingpublic string processorder(@valid order order, errors errors, sessionstatus sessionstatus, authentication authentication) { ... user user = (user) authentication.getprincipal(); order.setuser(user); ... }
3.使用securitycontextholder获取安全上下文
authentication authentication = securitycontextholder.getcontext().getauthentication(); user user = (user) authentication.getprincipal();
4.使用@authenticationprincipal注解方法
@postmappingpublic string processorder(@valid order order, errors errors,sessionstatus sessionstatus, @authenticationprincipal user user) { if (errors.haserrors()) { return "orderform"; } order.setuser(user); orderrepo.save(order); sessionstatus.setcomplete(); return "redirect:/"; }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: 刺激人中有讲究
下一篇: 灸足三里=吃老母鸡?