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能获得前端表单里面账号和密码,但获取不到验证码,所以使用这个方法:
https://blog.csdn.net/wzl19870309/article/details/70266939
https://www.cnblogs.com/phoenix-smile/p/5666686.html
,所以得写两个类:

package pers.lbw.digitalmall.config;

import org.springframework.security.web.authentication.WebAuthenticationDetails;
import pers.lbw.digitalmall.controllers.CodeController;

import javax.servlet.http.HttpServletRequest;

public class LoginWebAuthenticationDetails extends WebAuthenticationDetails {

	private String code;

	private String session_code;

	public LoginWebAuthenticationDetails(HttpServletRequest request) {
		super(request);
		this.code = (String) request.getParameter("code");
		this.session_code = (String) request.getSession().getAttribute(CodeController.codeSessionKey);
	}

	public String getCode() {
		return code;
	}

	public void setCode(String code) {
		this.code = code;
	}

	public String getSession_code() {
		return session_code;
	}

	public void setSession_code(String session_code) {
		this.session_code = session_code;
	}
}

package pers.lbw.digitalmall.config;

import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
public class LoginAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {
	@Override
	public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
		return new LoginWebAuthenticationDetails(context);
	}
}

注意要在对应的HttpSecurity http 对象中添加上这个.authenticationDetailsSource(loginAuthenticationDetailsSource)
然后就是那两个Handler了,通过这两个Handler我们可以返回json数据,实现前后端分离

package pers.lbw.digitalmall.config;

import com.alibaba.fastjson.JSON;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import pers.lbw.digitalmall.beans.JsonModel;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component("myAuthenticationSuccessHandler")
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
			throws IOException, ServletException {
		//什么都不做的话,那就直接调用父类的方法
		//super.onAuthenticationSuccess(request, response, authentication);

		//这里可以根据实际情况,来确定是跳转到页面或者json格式。
		//如果是返回json格式,那么我们这么写
		JsonModel jm = new JsonModel(200, "登陆成功", null, WebConfig.Host);
		response.setContentType("application/json;charset=UTF-8");
		response.getWriter().write(JSON.toJSONString(jm));

		//如果是要跳转到某个页面的,比如我们的那个whoim的则
		//new DefaultRedirectStrategy().sendRedirect(request, response, "/whoim");
	}
}

package pers.lbw.digitalmall.config;

import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import pers.lbw.digitalmall.beans.JsonModel;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

//登录失败的
@Component("myAuthenticationFailHandler")
public class MyAuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {
	@Override
	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
	                                    AuthenticationException exception) throws IOException {
		//以Json格式返回
		JsonModel jm = new JsonModel(0, exception.getMessage(), null, "/fore/user/login");
		response.setContentType("application/json;charset=UTF-8");
		response.addHeader("X-Frame-Options","SAMEORIGIN");//SAMEORIGIN:页面只能被本站页面嵌入到iframe或者frame中;
		PrintWriter writer = response.getWriter();
		writer.write(JSON.toJSONString(jm));
		writer.flush();
	}
}

在给出一个详情的关于springboot 集成 spring security的博文:
https://blog.csdn.net/qq_22771739/article/details/82010646

猜你喜欢

转载自blog.csdn.net/qq_22771739/article/details/84328333
今日推荐