SpringBoot Security多登陸頁面+登陸頁面驗證碼+Restful
1.首選從多登陸頁面開始
package pers.lbw.digitalmall.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework. security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import pers.lbw.digitalmall.services.impl.UserServiceImpl;
@EnableWebSecurity
@Configuration
public class MultiHttpSecurityConfig {
@Configuration
@Order(1)
public static class ForeConfigurationAdapter extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler myAuthenticationFailHandler;
@Autowired
private AuthenticationProvider authenticationProvider; //注入我們自己的AuthenticationProvider
@Autowired
private LoginAuthenticationDetailsSource loginAuthenticationDetailsSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/fore/**")//多HttpSecurity配置時必須設定這個,除最後一個外,因為不設定的話預設匹配所有,就不會執行到下面的HttpSecurity了
.formLogin()
.loginPage("/fore/user/login")//登陸介面頁面跳轉URL
.loginProcessingUrl("/fore/user/loginPost")//登陸介面發起登陸請求的URL
.successHandler(myAuthenticationSuccessHandler)
.failureHandler(myAuthenticationFailHandler)
.authenticationDetailsSource(loginAuthenticationDetailsSource)
.permitAll()//表單登入,permitAll()表示這個不需要驗證
.and()//Return the SecurityBuilder
.logout()
.logoutUrl("/fore/user/loginOut")//登出請求地址
.logoutSuccessUrl("/")
.and()
.authorizeRequests()//啟用基於 HttpServletRequest 的訪問限制,開始配置哪些URL需要被保護、哪些不需要被保護
.antMatchers("/user/**", "/detail/toDetailPage*").permitAll()//未登陸使用者允許的請求
.anyRequest().hasAnyRole("USER")//其他/fore路徑下的請求全部需要登陸,獲得USER角色
.and()
.headers().frameOptions().disable()//關閉X-Frame-Options
.and()
.csrf().disable();
}
}
@Configuration
@Order(2)
public static class AdminSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
public AdminSecurityConfigurationAdapter(UserServiceImpl userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/admin/**")
.formLogin()
.loginPage("/fore/user/login")//登陸介面頁面跳轉URL
.loginProcessingUrl("/fore/user/login111")//登陸介面發起登陸請求的URL
.defaultSuccessUrl("/manager/admin/index.html", true)
.failureUrl("/fore/user/login")//登陸失敗的頁面跳轉URL
.permitAll()//表單登入,permitAll()表示這個不需要驗證
.and()//Return the SecurityBuilder
.authorizeRequests()//啟用基於 HttpServletRequest 的訪問限制,開始配置哪些URL需要被保護、哪些不需要被保護
.antMatchers("/admin/**").hasAnyRole("ADMIN")//其他/fore路徑下的請求全部需要登陸,獲得USER角色
.and()
.csrf().disable();
}
}
@Configuration
@Order(3)
public static class OtherSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()//啟用基於 HttpServletRequest 的訪問限制,開始配置哪些URL需要被保護、哪些不需要被保護
.antMatchers("/","/code/**","/css/**", "/img/**", "/js/**").permitAll()//其他請求放行
.and()
.csrf()
.disable();//未登陸使用者允許的請求
}
}
}
上面的程式碼先只講多登陸頁面配置:
配置多個登陸頁的方法已經在上面演示出,主要是通過寫一個類MultiHttpSecurityConfig,然後在加上@EnableWebSecurity和@Configuration註解,其中多個HttpSecurity的配置是通過多個繼承了WebSecurityConfigurerAdapter的靜態內部類實現的,關於這個的具體說明:https://blog.csdn.net/qq_22771739/article/details/84308847
https://blog.csdn.net/qq_22771739/article/details/84308908
https://blog.csdn.net/qq_22771739/article/details/84308214
,如程式碼中所強調的:多HttpSecurity配置時必須設定這個,除最後一個外,因為不設定的話預設匹配所有,就不會執行到下面的HttpSecurity了
2.登陸頁面驗證碼和Restful
為了實現驗證碼和Restful,我們得用我們自己的AuthenticationProvider,新建MyAuthenticationProvider繼承AuthenticationProvider
package pers.lbw.digitalmall.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import pers.lbw.digitalmall.beans.AnyUser;
import javax.annotation.Resource;
import java.util.Collection;
@Component
public class MyAuthenticationProvider implements AuthenticationProvider {
/**
* 注入我們自己定義的使用者資訊獲取物件
*/
@Resource(name = "userServiceImpl")
private UserDetailsService userDetailService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String userName = authentication.getName();// 這個獲取表單輸入中返回的使用者名稱;
String password = (String) authentication.getCredentials();// 這個是表單中輸入的密碼;
LoginWebAuthenticationDetails details = (LoginWebAuthenticationDetails) authentication.getDetails();//拿到表單的其他資訊
String code = details.getCode();
String session_code = details.getSession_code();
System.err.println("userName:"+userName+" password:"+password);
System.err.println("code:"+code+" session_code:"+session_code);
//判斷驗證碼是否正確
if(session_code==null||!session_code.equalsIgnoreCase(code)){
throw new AuthenticationException("驗證碼錯誤!"){};
}
// 這裡構建來判斷使用者是否存在
AnyUser user = (AnyUser) userDetailService.loadUserByUsername(userName); // 這裡呼叫我們的自己寫的獲取使用者的方法;
//判斷密碼是否正確
//實際應用中,我們的密碼一般都會加密,以Md5加密為例,這裡省略了
if(!user.getPassword().equals(password)){
throw new AuthenticationException("密碼錯誤!"){};
}
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
// 構建返回的使用者登入成功的token
return new UsernamePasswordAuthenticationToken(user, password, authorities);
}
@Override
public boolean supports(Class<?> authentication) {
// 這裡直接改成retrun true;表示是支援這個執行
return true;
}
}
先看UserDetailsService 這個介面,這是一個security定義的介面,需要我們自己實現,功能是根據使用者名稱查到資料庫中對應的使用者,如果沒有就丟擲UsernameNotFoundException就行 ,這是第一個異常,一共三個,後面我統一講。我用UserServiceImpl實現了UserDetailsService,除了loadUserByUsername方法外,其他的都是我原來沒有整合SpringSecurity時建立的,可以不用看。
package pers.lbw.digitalmall.services.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import pers.lbw.digitalmall.beans.AnyUser;
import pers.lbw.digitalmall.beans.User;
import pers.lbw.digitalmall.dao.UserDao;
import pers.lbw.digitalmall.services.UserService;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserServiceImpl implements UserService,UserDetailsService {
@Autowired
UserDao ud;
@Override
public User login(User u) {
return ud.login(u);
}
@Override
public User register(User u) {
ud.register(u);
return ud.login(u);
}
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User u = new User();
u.setUsername(s);
u = login(u);//登陸
if (u == null) {
throw new UsernameNotFoundException("使用者名稱'" + s + "'未找到!");
}
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
//對應的許可權新增
if(u.getRole()==0){
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));//注意一定要以ROLE_打頭,另外一邊就要寫.hasRole("USER")
}else{
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));//注意一定要以ROLE_打頭,另外一邊就要寫.hasRole("ADMIN")
}
//如果不為空構造一個認證user
AnyUser user=new AnyUser(u.getUsername(),u.getPassword(),authorities);
user.setId((long)u.getId());
user.setBirthday(u.getBirthday());
user.setEmail(u.getEmail());
user.setName(u.getName());
user.setNickname(u.getNickname());
user.setPhone(u.getPhone());
user.setPlace(u.getPlace());
user.setRole(u.getRole());
user.setSex(u.getSex());
user.setStreet(u.getStreet());
return user;
}
}
UserDetailsService 接口裡面就一個抽象方法:loadUserByUsername,它的返回值是:UserDetails,這也是一個介面,我們需要建立實現類,org.springframework.security.core.userdetails.User實現了UserDetails,我們可以直接通過繼承它來減少程式碼量,注意:這裡至少得有id這個屬性,而其他的屬性都不是必須的,我寫上去只是為了在使用者登陸完成之後我在其他控制器能通過SecurityContextHolder.getContext().getAuthentication().getPrincipal();獲取這個bean,然後從而拿到使用者的資訊,避免查詢資料庫。
package pers.lbw.digitalmall.beans;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.sql.Date;
import java.util.Collection;
/**
* 自定義的 User 物件
* 此 User 類不是我們的資料庫裡的使用者類,是用來安全服務的
*/
//import org.springframework.security.core.userdetails.User;
public class AnyUser extends User {
private Long id;
private String name;
private String nickname;
private Integer sex;
private String phone;
private String email;
private Date birthday;
private Integer place;
private String street;
private Integer role;
public AnyUser(
String username,
String password,
Collection<? extends GrantedAuthority> authorities
) {
super(username, password, authorities);
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public Integer getSex() {
return sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Integer getPlace() {
return place;
}
public void setPlace(Integer place) {
this.place = place;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public Integer getRole() {
return role;
}
public void setRole(Integer role) {
this.role = role;
}
}
講完這個AnyUser後,我們來看MyAuthenticationProvider中的throw new UsernameNotFoundException(“使用者名稱’” + s + “'未找到!”);和throw new AuthenticationException(“密碼錯誤!”){};這兩個異常,在加上我上面提及到的一個異常,一共三個,這個三個任意一個觸發都成使程式進入myAuthenticationFailHandler,如果沒有觸發則進入myAuthenticationSuccessHandler,這兩個等下在講,現在來看MyAuthenticationProvider 類,
String userName = authentication.getName();// 這個獲取表單輸入中返回的使用者名稱;
String password = (String) authentication.getCredentials();// 這個是表單中輸入的密碼;
這兩個api