написать впереди
-
О весенней безопасностиМодуль аутентификации и полномочий веб-системы также можно рассматривать как инфраструктуру системы, и почти любая служба Интернета будет иметь требования в этом отношении. В области Java EE зрелые решения для обеспечения безопасности обычно включают Apache Shiro и Spring Security. Простота и легкость использования Apache Shiro также являются большим преимуществом, но его функции по-прежнему намного менее эффективны, чем Spring Security. Spring Security может обеспечить декларативный контроль доступа к безопасности для приложений Spring, предоставляя серию bean-компонентов, которые можно настроить в контексте приложения Spring, и используя Spring IoC и AOP, а также другие функциональные возможности для обеспечения декларативного контроля доступа к безопасности для функций прикладных систем, уменьшая много дублирования работы.
-
О JWTJSON Web Token (JWT) — открытый стандарт на основе JSON (RFC 7519) для передачи информации между сетевыми приложениями.Он используется в качестве объекта JSON для безопасной передачи информации между различными системами.Основные сценарии использования, как правило, используют для передачи аутентифицированного информация об удостоверении пользователя между поставщиком удостоверений и поставщиком услуг.Для научно-популярной информации о JWT вы можете посмотреть «Вводный курс JSON Web Token» г-на Руана Ифэна.
В этой статье Spring Security и JWT объединяются для создания простой системы разрешений.
Экспериментальная среда этой статьи выглядит следующим образом:
- Весенняя загрузочная версия:
2.0.6.RELEASE
- ИДЕ:
IntelliJ IDEA 2018.2.4
Кроме того, экспериментальный код этой статьи размещен в конце текста и его нужно подобрать самостоятельно.
МожетНажмитеилисканированиеследующеебудь остороженподписаться на официальный аккаунт автораCodeSheep, получить большеПрагматичный, понятный, воспроизводимыйИсходный текст ↓↓↓
Дизайн пользователей и ролей
Для того, чтобы упростить рассмотрение эксперимента в этой статье, мы собираемся сделать следующий план:
- Создайте минимальную таблицу ролей
role
,включают角色ID
и角色名称
- Создайте минимальную пользовательскую таблицу
user
,включают用户ID
,用户名
,密码
- Создайте таблицу связей «один ко многим» между пользователями и ролями
user_roles
Пользователь может иметь несколько ролей
Создайте веб-проект, поддерживаемый Spring Security и JWT.
pom.xml
Ввести зависимости, необходимые для Spring Security и JWT.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
- Добавьте необходимую конфигурацию, такую как база данных и JPA, в файл конфигурации проекта.
server.port=9991
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://121.196.XXX.XXX:3306/spring_security_jwt?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=XXXXXX
logging.level.org.springframework.security=info
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jackson.serialization.indent_output=true
- Создание пользователей, ролевых сущностей
Объект пользователя Пользователь:
/**
* @ www.codesheep.cn
* 20190312
*/
@Entity
public class User implements UserDetails {
@Id
@GeneratedValue
private Long id;
private String username;
private String password;
@ManyToMany(cascade = {CascadeType.REFRESH},fetch = FetchType.EAGER)
private List<Role> roles;
...
// 下面为实现UserDetails而需要的重写方法!
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
for (Role role : roles) {
authorities.add( new SimpleGrantedAuthority( role.getName() ) );
}
return authorities;
}
...
}
Созданный здесь класс User наследует интерфейс UserDetails Spring Security, таким образом становясь пользователем, совместимым с Security, то есть, наследуя UserDetails, можно реализовать соответствующие функции безопасности в Security.
Ролевая сущность Роль:
/**
* @ www.codesheep.cn
* 20190312
*/
@Entity
public class Role {
@Id
@GeneratedValue
private Long id;
private String name;
... // 省略 getter和 setter
}
- Создать класс инструмента JWT
Он в основном используется для выполнения различных операций с токеном JWT, таких как создание токена, проверка токена, обновление токена и т. д.
/**
* @ www.codesheep.cn
* 20190312
*/
@Component
public class JwtTokenUtil implements Serializable {
private static final long serialVersionUID = -5625635588908941275L;
private static final String CLAIM_KEY_USERNAME = "sub";
private static final String CLAIM_KEY_CREATED = "created";
public String generateToken(UserDetails userDetails) {
...
}
String generateToken(Map<String, Object> claims) {
...
}
public String refreshToken(String token) {
...
}
public Boolean validateToken(String token, UserDetails userDetails) {
...
}
... // 省略部分工具函数
}
- Создайте Token filter для обработки Token для каждого внешнего запроса к интерфейсу
/**
* @ www.codesheep.cn
* 20190312
*/
@Component
public class JwtTokenFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal ( HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String authHeader = request.getHeader( Const.HEADER_STRING );
if (authHeader != null && authHeader.startsWith( Const.TOKEN_PREFIX )) {
final String authToken = authHeader.substring( Const.TOKEN_PREFIX.length() );
String username = jwtTokenUtil.getUsernameFromToken(authToken);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(request, response);
}
}
- Служебная деловая переписка
В основном он включает в себя два основных направления входа и регистрации пользователей.
public interface AuthService {
User register( User userToAdd );
String login( String username, String password );
}
/**
* @ www.codesheep.cn
* 20190312
*/
@Service
public class AuthServiceImpl implements AuthService {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserRepository userRepository;
// 登录
@Override
public String login( String username, String password ) {
UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken( username, password );
final Authentication authentication = authenticationManager.authenticate(upToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
final UserDetails userDetails = userDetailsService.loadUserByUsername( username );
final String token = jwtTokenUtil.generateToken(userDetails);
return token;
}
// 注册
@Override
public User register( User userToAdd ) {
final String username = userToAdd.getUsername();
if( userRepository.findByUsername(username)!=null ) {
return null;
}
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
final String rawPassword = userToAdd.getPassword();
userToAdd.setPassword( encoder.encode(rawPassword) );
return userRepository.save(userToAdd);
}
}
- Написание класса конфигурации Spring Security (очень важно)
Это очень всеобъемлющий класс конфигурации, в основном переопределяющийWebSecurityConfigurerAdapter
частьconfigure
Конфигурация для реализации пользовательских частей.
/**
* @ www.codesheep.cn
* 20190312
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Bean
public JwtTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtTokenFilter();
}
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure( AuthenticationManagerBuilder auth ) throws Exception {
auth.userDetailsService( userService ).passwordEncoder( new BCryptPasswordEncoder() );
}
@Override
protected void configure( HttpSecurity httpSecurity ) throws Exception {
httpSecurity.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // OPTIONS请求全部放行
.antMatchers(HttpMethod.POST, "/authentication/**").permitAll() //登录和注册的接口放行,其他接口全部接受验证
.antMatchers(HttpMethod.POST).authenticated()
.antMatchers(HttpMethod.PUT).authenticated()
.antMatchers(HttpMethod.DELETE).authenticated()
.antMatchers(HttpMethod.GET).authenticated();
// 使用前文自定义的 Token过滤器
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
httpSecurity.headers().cacheControl();
}
}
- Написать тестовый контроллер
Логин и зарегистрированный контроллер:
/**
* @ www.codesheep.cn
* 20190312
*/
@RestController
public class JwtAuthController {
@Autowired
private AuthService authService;
// 登录
@RequestMapping(value = "/authentication/login", method = RequestMethod.POST)
public String createToken( String username,String password ) throws AuthenticationException {
return authService.login( username, password ); // 登录成功会返回JWT Token给用户
}
// 注册
@RequestMapping(value = "/authentication/register", method = RequestMethod.POST)
public User register( @RequestBody User addedUser ) throws AuthenticationException {
return authService.register(addedUser);
}
}
Затем напишите контроллер для проверки разрешений:
/**
* @ www.codesheep.cn
* 20190312
*/
@RestController
public class TestController {
// 测试普通权限
@PreAuthorize("hasAuthority('ROLE_NORMAL')")
@RequestMapping( value="/normal/test", method = RequestMethod.GET )
public String test1() {
return "ROLE_NORMAL /normal/test接口调用成功!";
}
// 测试管理员权限
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
@RequestMapping( value = "/admin/test", method = RequestMethod.GET )
public String test2() {
return "ROLE_ADMIN /admin/test接口调用成功!";
}
}
Вот два тестовых интерфейса для тестирования проблем, связанных с разрешениями, где интерфейс/normal/test
Требуется, чтобы у пользователя была обычная роль (ROLE_NORMAL
) можно получить, в то время как интерфейс/admin/test
требует, чтобы пользователь имел роль администратора (ROLE_ADMIN
) можно получить доступ.
Далее запускаем проект, экспериментируем и тестируем, чтобы увидеть эффект
Экспериментальная проверка
-
В начале статьи мы находимся в пользовательской таблице
user
вставил имя пользователяcodesheep
записей и в таблице ролей пользователейuser_roles
пользователямcodesheep
Нормальная роль назначена(ROLE_NORMAL
) и роль администратора (ROLE_ADMIN
) -
Затем войдите в систему пользователя и получите токен JWT, выданный пользователю в фоновом режиме.
- Затем войдите в интерфейс проверки разрешений.
Прямой доступ без токена требует общих ролей (ROLE_NORMAL
)Интерфейс/normal/test
Он прямо подскажет, что доступ недоступен:
Для доступа с токеном требуются общие роли (ROLE_NORMAL
)Интерфейс/normal/test
Вызов будет успешным:
По той же причине текущий пользователь имеет роль администратора, поэтому для доступа требуется роль администратора (ROLE_ADMIN
)Интерфейс/admin/test
также удается:
Далее мы получаем пользователя из таблицы ролей пользователейcodesheep
удалите права администратора, а затем получите доступ к интерфейсу/admin/test
, вы обнаружите, что доступ запрещен из-за отсутствия разрешений:
После серии экспериментов он также оправдал наши ожидания!
напиши в конце
В этой статье довольно много всего, и, наконец, мы также поместили экспериментальный исходный код этой статьи вна Гитхабе, вы можете взять его сами, если вам это нужно:Адрес загрузки исходного кода
Из-за ограниченных возможностей, если есть ошибки или неуместность, пожалуйста, критикуйте и исправьте их, учитесь и обменивайтесь мнениями вместе!
- Мой личный блог:Программа CodeSheep Овца