Spring Security是一个专门针对基于Spring的项目的安全框架。
下面这些记录仅是学习中的一些记录,单篇并不完整,具体的、不明白的可以看前面的记录。
概述
在Spring Boot中,对于Security,只要在pom.xml文件中添加了依赖就会自动使用了Security,在浏览器中访问自动弹出自带的登陆界面要求输入账号密码进行登陆,用户名默认为user,密码是启动时自动生成的密码。
如果使用自己的登陆界面自己的账号密码进行登陆,需要对Security进行配置、实现AuthenticationProvider、实现UserDetailsService,同时提供UserData类、登陆页面html。
UserDetailsService主要是重写loadUserByUsername方法,通过登陆页面传入的用户名,匹配内存固化的用户名密码,或者匹配数据库里的用户名密码,加上权限填充到UserData,返回给AuthenticationProvider使用。
AuthenticationProvider可以不用,通过默认的方式进行处理。这里是使用AuthenticationProvider。主要是重写authenticate方法,获取到UserDetailsService传递过来的用户密码和登陆输入的用户名和密码,两者进行匹配认证,包括加密处理,从而判断登陆成功与否。
实现
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
UserData.java
package com.net.security;
import java.io.Serializable;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
public class UserData implements Serializable, UserDetails {
/**
*
*/
private static final long serialVersionUID = 1L;
private String username;
private String password;
private String role;
private boolean accountNonExpired;
private boolean accountNonLocked;
private boolean credentialsNonExpired;
private boolean enabled;
public UserData(String username, String password, String role, boolean accountNonExpired,
boolean accountNonLocked,boolean credentialsNonExpired, boolean enabled)
{
// TODO Auto-generated constructor stub
this.username = username;
this.password = password;
this.role = role;
this.accountNonExpired = accountNonExpired;
this.accountNonLocked = accountNonLocked;
this.credentialsNonExpired = credentialsNonExpired;
this.enabled = enabled;
}
// 这是权限
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// TODO Auto-generated method stub
return AuthorityUtils.commaSeparatedStringToAuthorityList(role);
}
@Override
public String getPassword() {
// TODO Auto-generated method stub
return password;
}
@Override
public String getUsername() {
// TODO Auto-generated method stub
return username;
}
@Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return credentialsNonExpired;
}
@Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return enabled;
}
}
SecurityUserDetailsService.java(UserDetailsService)
package com.net.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import com.net.domain.User;
import com.net.security.UserData;
import com.net.service.UserService;
//登陆的用户名传入这里,可以匹配固定的内存用户名和密码,也可以匹配数据库里面查询处理的用户名和密码。通过返回值传递给别人使用
@Component
public class SecurityUserDetailsService implements UserDetailsService {
@Autowired
@Qualifier("userService")
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
UserData userData=null;
// TODO 根据用户名,查找到对应的密码,与权限
System.out.println("get name:" + username);
User user=userService.loginWithLoginname(username);
if(user != null)
{
System.out.println("找到用户名和密码:"+ user.toString());
//返回的用户信息如下,参数分别是:用户名,密码,用户权限
if(user.getLoginname().equals("admin"))
userData=new UserData(user.getLoginname(), user.getPassword(), "ROLE_ADMIN", true,true,true,true);//ROLE_ADMIN
else
userData=new UserData(user.getLoginname(), user.getPassword(), "ROLE_USER", true,true,true,true); //ROLE_USER
}
else
{
System.out.println("没有找到用户名和密码");
}
return userData;
}
}
SecurityAuthenticationProvider.java(AuthenticationProvider)
package com.net.security;
import java.util.Collection;
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 com.net.security.UserData;
//认证处理。通过UserDetailsService获取到用户名和密码,这里可以获取到登陆输入的用户名和密码,两者进行匹配认证,包括加密处理,从而判断登陆成功与否
@Component
public class SecurityAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserDetailsService userDetailService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// TODO Auto-generated method stub
String userName = authentication.getName(); // 这个获取表单输入中返回的用户名;
String password = authentication.getCredentials().toString(); // 这个是表单中输入的密码;
// 这里构建来判断用户是否存在和密码是否正确
System.out.println("username:"+ userName);
System.out.println("password:"+ password);
UserData userData = (UserData) userDetailService.loadUserByUsername(userName);
if (userData == null)
{
throw new BadCredentialsException("用户名不存在");
}
System.out.println("userData username:"+ userData.getUsername());
System.out.println("userData password:"+ userData.getPassword());
if (!userData.getPassword().equals(password))
{
System.out.println("密码不正确");
throw new BadCredentialsException("密码不正确");
}
Collection<? extends GrantedAuthority> authorities = userData.getAuthorities();
// 构建返回的用户登录成功的token
System.out.println("登陆成功");
return new UsernamePasswordAuthenticationToken(userData, password, authorities);
}
@Override
public boolean supports(Class<?> authentication) {
// TODO Auto-generated method stub
// 这里直接改成retrun true;表示是支持这个执行
return true;
}
}
SecurityConfig.java
package com.net.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import com.net.security.SecurityAuthenticationProvider;
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable();
http.formLogin()
.loginPage("/login") //登陆界面,用于输入用户和密码。跟控制器login中返回的名称相同
.loginProcessingUrl("/loginProcess") //登陆界面提交表单的action对应的名称
.defaultSuccessUrl("/index") //验证成功后默认的跳转动作
.failureUrl("/loginFail") //验证失败后跳转的动作
.permitAll() //表示这个不需要验证登录页面/登录失败页面
.and()
.authorizeRequests()
.antMatchers("/assets/*","/css/*","/image/*","/font/*","/js/*","/Widget/*").permitAll()
.antMatchers(HttpMethod.POST).hasRole("ADMIN")
//.antMatchers("/index").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.csrf().disable();
}
@Override
public void configure(WebSecurity web) throws Exception {
//解决静态资源被拦截的问题
web.ignoring().antMatchers("/assets/**");
web.ignoring().antMatchers("/css/**");
web.ignoring().antMatchers("/images/**");
web.ignoring().antMatchers("/font/**");
web.ignoring().antMatchers("/js/**");
web.ignoring().antMatchers("/Widget/**");
}
@Autowired
private SecurityAuthenticationProvider provider;//注入我们自己的AuthenticationProvider
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//从内存中获取
//auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("user").password(new BCryptPasswordEncoder().encode("123456")).roles("admin");
//从接口中获取
//auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
auth.authenticationProvider(provider);
}
}
下面是测试代码
LoginControl.java
package com.net.web;
import java.util.List;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class LoginControl {
private static Logger LOGGER = LoggerFactory.getLogger(LoginControl.class);
@RequestMapping(value="/login")
public String login()
{
LOGGER.info("login");
return "login";
}
@RequestMapping(value="/index")
public String index(Model model)
{
LOGGER.info("index");
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
LOGGER.info("当前用户:"+auth.getName());
model.addAttribute("cur_user",auth.getName());
return "index";
}
}
login.html
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>登陆</title>
</head>
<body>
<h4>用户登陆<font color="red"><span> </span><span th:text="${message}"></span></font></h4>
<form id="loginForm" class="" action="loginProcess" method="post">
<i>用户名</i><input name="username" type="text" id="username"/>
<br>
<i>密<span> </span>码</i><input name="password" type="password" id="password"/>
<br>
<br>
<label><input type="checkbox" id="remember" class="ace"><span>保存密码</span><span> </span></label>
<button type="submit" id="login_btn">登<span> </span>陆</button>
</form>
</body>
</html>
<script>
window.onload = function(){
var oForm = document.getElementById('loginForm');
var oUser = document.getElementById('username');
var oPswd = document.getElementById('password');
var oRemember = document.getElementById('remember');
//页面初始化时,如果帐号密码cookie存在则填充
if(getCookie('username') && getCookie('password')){
oUser.value = getCookie('username');
oPswd.value = getCookie('password');
oRemember.checked = true;
}
//复选框勾选状态发生改变时,如果未勾选则清除cookie
oRemember.onchange = function(){
if(!this.checked){
delCookie('username');
delCookie('password');
}
};
//表单提交事件触发时,如果复选框是勾选状态则保存cookie
oForm.onsubmit = function(){
if(remember.checked){
setCookie('username',oUser.value,7); //保存帐号到cookie,有效期7天
setCookie('password',oPswd.value,7); //保存密码到cookie,有效期7天
}
};
};
//设置cookie
function setCookie(name,value,day){
var date = new Date();
date.setDate(date.getDate() + day);
document.cookie = name + '=' + value + ';expires='+ date;
};
//获取cookie
function getCookie(name){
var reg = RegExp(name+'=([^;]+)');
var arr = document.cookie.match(reg);
if(arr){
return arr[1];
}else{
return '';
}
};
//删除cookie
function delCookie(name){
setCookie(name,null,-1);
};
</script>
index.html
<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<title>用户登录</title>
</head>
<body>
<h3>登陆成功,<span th:text="${cur_user}"></span></h3>
<div sec:authorize="hasRole('ROLE_ADMIN')">
<p>ROLE_ADMIN</p>
</div>
<div sec:authorize="hasRole('ROLE_USER')">
<p>ROLE_USER</p>
</div>
</body>
</html>
因为index.html使用了 sec:authorize="hasRole('ROLE_ADMIN')"进行权限控制显示,pom.xml需要添加依赖:
<!-- 没有这个依赖html页面无法使用sec标签 -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
运行测试
在浏览器输入http://localhost:8080/login
回车出现下面界面
输入admin 123456登陆失败,输入root 123456登陆成功,并跳转到index下显示。
总结
Security配置里面直接决定了运行方式,需要跟Control和html页面进行对应的匹配,否则运行出错。
.loginPage("/login") //登陆界面,用于输入用户和密码。跟控制器login中返回的名称相同
.loginProcessingUrl("/loginProcess") //登陆界面提交表单的action对应的名称
.defaultSuccessUrl("/index") //验证成功后默认的跳转动作
.failureUrl("/loginFail") //验证失败后跳转的动作
.permitAll() //表示这个不需要验证登录页面/登录失败页面
.and()
.authorizeRequests()
.antMatchers("/assets/*","/css/*","/image/*","/font/*","/js/*","/Widget/*").permitAll()
.antMatchers(HttpMethod.POST).hasRole("ADMIN")
//.antMatchers("/index").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.csrf().disable();
<form id="loginForm" class="" action="loginProcess" method="post">
xxxx
</form>
@RequestMapping(value="/login")
@RequestMapping(value="/index")
以上这些都是成功运行比较关键的地方。