首先,引入依赖:html
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
引入此依赖以后,你的web程序将拥有如下功能:java
springsecurity配置项,最好保存在一个单独的配置类中:web
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { }
配置用户认证方式spring
首先,要解决的就是用户注册,保存用户的信息。springsecurity提供四种存储用户的方式:数据库
使用其中任意一种方式,须要覆盖configure(AuthenticationManagerBuilder auth)
方法:安全
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { } }
1.基于内存session
@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.基于JDBCapp
@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";
固然,你能够对这些语句进行一下修改:ide
@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
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对象注入控制器方法
使用@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:/"; }