1. 程式人生 > >SpringBoot Security多登陸頁面+登陸頁面驗證碼+Restful

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