您好,登錄后才能下訂單哦!
首先,引入依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
引入此依賴之后,你的web程序?qū)碛幸韵鹿δ埽?/p>
配置SpringSecurity
springsecurity配置項,最好保存在一個單獨的配置類中:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { }
配置用戶認證方式
首先,要解決的就是用戶注冊,保存用戶的信息。springsecurity提供四種存儲用戶的方式:
使用其中任意一種方式,需要覆蓋configure(AuthenticationManagerBuilder auth)方法:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { } }
1.基于內(nèi)存
@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的方式,你必須有一些特定表表,而且字段滿足其查詢規(guī)則:
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=?");
這有一個問題,你數(shù)據(jù)庫中的密碼可能是一種加密方式加密過的,而用戶傳遞的是明文,比較的時候需要進行加密處理,springsecurity也提供了相應(yīng)的功能:
@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接口的實現(xiàn),其默認提供了一些實現(xiàn),如果都不滿足,你可以實現(xiàn)這個接口:
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.用戶自定義方式(最常用)
首先,你需要一個用戶實體類,它實現(xiàn)UserDetails接口,實現(xiàn)這個接口的目的是為框架提供更多的信息,你可以把它看作框架使用的實體類:
@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對象就成,無論是基于文件、基于數(shù)據(jù)庫、還是基于LDAP,剩下的對比判斷交個框架完成:
@Service public class UserService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { return null; } }
最后,進行應(yīng)用:
@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()); }
配置認證路徑
知道了如何認證,但現(xiàn)在有幾個問題,比如,用戶登錄頁面就不需要認證,可以用configure(HttpSecurity http)對認證路徑進行配置:
@Override protected void configure(HttpSecurity http) throws Exception { }
你可以通過這個方法,實現(xiàn)以下功能:
1.保護請求
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/design", "/orders").hasRole("ROLE_USER") .antMatchers(“/”, "/**").permitAll(); }
要注意其順序,除了hasRole和permitAll還有其它訪問認證方法:
方法 | 作用 |
---|---|
access(String) | 如果給定的SpEL表達式的計算結(jié)果為true,則允許訪問 |
anonymous() | 允許訪問匿名用戶 |
authenticated() | 允許訪問經(jīng)過身份驗證的用戶 |
denyAll() | 無條件拒絕訪問 |
fullyAuthenticated() | 如果用戶完全通過身份驗證,則允許訪問 |
hasAnyAuthority(String...) | 如果用戶具有任何給定權(quán)限,則允許訪問 |
hasAnyRole(String...) | 如果用戶具有任何給定角色,則允許訪問 |
hasAuthority(String) | 如果用戶具有給定權(quán)限,則允許訪問 |
hasIpAddress(String) | 如果請求來自給定的IP地址,則允許訪問 |
hasRole(String) | 如果用戶具有給定角色,則允許訪問 |
not() | 否定任何其他訪問方法的影響 |
permitAll() | 允許無條件訪問 |
rememberMe() | 允許通過remember-me進行身份驗證的用戶訪問 |
大部分方法是為特定方式準備的,但是access(String)可以使用SpEL進一些特殊的設(shè)置,但其中很大一部分也和上面的方法相同:
表達式 | 作用 |
---|---|
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}"/>
當然,你也可以關(guān)閉,但是不建議這樣做:
.and() .csrf() .disable()
知道用戶是誰
僅僅控制用戶登錄有時候是不夠的,你可能還想在程序的其它地方獲取已經(jīng)登錄的用戶信息,有幾種方式可以做到:
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:/"; }
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。