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