利用spring security 实现简单的登陆验证,并且在登陆失败或者成功后进行对前端返回的处理。
1.准备(数据库配置等)
本例子使用的Mysql + Hibernate
2.引入maven依赖
本例子需要的依赖有
基本的mysql,jpa,还有spring security的oauth2,jwt
3.新建表User,UserRole
创建entity:User和UserRole,在本例子中实际上只有User一个表就够了,毕竟只是验证用户名和密码嘛,但是我习惯每次创建User就手痒价格Role的表。
2个表多对多的关系也在代码中有用Hibernate写了,感兴趣的可以在Git上看一下。
@Entity
@Table(name = "sys_user")
public class User {
private String userName;
private String userDescription;
private String password;
private List<UserRole> roles;
@Id
@Column(name = "user_name")
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Column(name = "user_desc")
public String getUserDescription() {
return userDescription;
}
public void setUserDescription(String userDescription) {
this.userDescription = userDescription;
}
@Column(name = "password")
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@ManyToMany
@JoinTable(name = "sys_user_role",
joinColumns = @JoinColumn(name = "user_name", referencedColumnName = "user_name", updatable = false, insertable = false),
inverseJoinColumns = @JoinColumn(name = "role_code", referencedColumnName = "role_code", updatable = false, insertable = false))
public List<UserRole> getRoles() {
return roles;
}
public void setRoles(List<UserRole> roles) {
this.roles = roles;
}
}
4.User Repository
此处只需要添加两个方法,findByUserName在UserDetialService load user的信息时候用,一个是测试用。
@Repository
public interface UserRepository extends JpaRepository<User, String> {
User findByUserName(String userName);
@Query(value = "select r.roleCode from User u inner join u.roles as r where u.userName = :userName")
List<String> queryUserOwnedRoleCodes(@Param(value = "userName") String userName);
}
5.新建DatabaseUserDetailsService
新建DatabaseUserDetailsService继承UserDetailsService,并重写loadUserByUsername方法,在用户登陆时,spring会调用这个方法去获得user的信息(密码等),以对比页面传过来的用户名和密码是否正确。
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
User user = userRepository.findByUserName(userName);
if (user == null) {
//throw exception inform front end not this user
throw new UsernameNotFoundException("user + " + userName + "not found.");
}
List<String> roleCodeList = userRepository.queryUserOwnedRoleCodes(userName);
List<GrantedAuthority> authorities =
roleCodeList.stream().map(e -> new SimpleGrantedAuthority(e)).collect(Collectors.toList());
UserDetails userDetails = new org.springframework.security.core.userdetails.User(
user.getUserName(),user.getPassword(),authorities);
return userDetails;
}
6.新建WebSecuerityConfig
建立一个WebSecuerityConfig类继承WebSecurityConfigurerAdapter,并重写两个configure方法,
配置各种访问权限限制以及添加处理类
(1)不需要限制的用permitAll()放行即可。
(2).successHandler() 和 .failureHandler() 是配置登录失败或成功时的处理,后面有写这两个类的实现。
(3).authenticationEntryPoint()是没有登录就请求资源时的处理。
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {
@Configuration
public static class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("databaseUserDetailService")
private DatabaseUserDetailService userDetailsService;
@Autowired
@Qualifier("authenticationSuccessHandler")
private AuthenticationSuccessHandler successHandler;
@Autowired
@Qualifier("authenticationFailHandler")
private AuthenticationFailHandler failHandler;
@Autowired
@Qualifier("authenticationEntryPointImpl")
private AuthenticationEntryPoint entryPoint;
@Override
public void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and().csrf().disable()
.authorizeRequests()
.antMatchers("/v2/api-docs/**").permitAll()
.anyRequest().authenticated()
.and().formLogin().loginProcessingUrl("/api/login")
.successHandler(successHandler)
.failureHandler(failHandler)
.and().exceptionHandling().authenticationEntryPoint(entryPoint);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
}
7.登陆失败或成功的处理。
未登录就请求资源时,spring会交给AuthenticationEntryPoint处理。
登陆成功之后,spring会跳到AuthenticationFailHandler。
登陆失败之后,spring会跳到AuthenticationSuccessHandler。
所以我们要继承这两个方法,把想要返回给页面的信息在这两个类中写一下。
@Service("authenticationEntryPointImpl")
public class AuthenticationEntryPointImplimplements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, AuthenticationException e)throws IOException {
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
}
}
@Service("authenticationSuccessHandler")
public class AuthenticationSuccessHandlerextends SavedRequestAwareAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response
, Authentication authentication)throws IOException {
logger.info("User: " + request.getParameter("username") +" Login successfully.");
this.returnJson(response);
}
private void returnJson(HttpServletResponse response)throws IOException {
response.setStatus(HttpServletResponse.SC_OK);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().println("{\"exceptionId\":\"null\",\"messageCode\":\"200\"," +
"\"message\": \"Login successfully.\",\"serverTime\": " + System.currentTimeMillis() +"}");
}
}
@Service("authenticationFailHandler")
public class AuthenticationFailHandlerextends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)throws IOException, ServletException {
this.returnJson(response,exception);
}
private void returnJson(HttpServletResponse response,
AuthenticationException exception)throws IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().println("{\"exceptionId\":\"null\",\"messageCode\":\"401\"," +
"\"message\": \""+ exception.getMessage() +"\",\"serverTime\": " + System.currentTimeMillis() +"}");
}
}
8.Postman 测试
这是我数据库中存在的数据
没有登录直接发送普通时:
密码或用户名输入错误时,
用户名密码都正确时:
更详细的springboot权限验证参考另一篇:
(Spring Boot+Spring security+Jwt实现token控制权限)